Repository: gradio-app/gradio

Issue:
`gr.Radio` and `gr.CheckboxGroup` choices display text.
- [x] I have searched to see if a similar issue already exists.


**Is your feature request related to a problem? Please describe.**  
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

Usually for the `gr.Radio` and `gr.CheckboxGroup`, the choices are  different for the program to process and UI display to the user. For example, in a program, it might be easier to have choices named like ["openai_completion", "openai_chat"], but it would be great to have the UI display as ["OpenAI (Completion)", "OpenAI (Chat)"].

**Describe the solution you'd like**  
A clear and concise description of what you want to happen.

 It would be great if there could be an additional argument 
 e.g. `choices_display` for  `gr.Radio` and `gr.CheckboxGroup` that can be used specifically for UI display.

**Additional context**  
Add any other context or screenshots about the feature request here.



Discussion:
Similar to #3019, we should probably tackle them together

Resolving patch:
diff --git a/gradio/components/checkboxgroup.py b/gradio/components/checkboxgroup.py
--- a/gradio/components/checkboxgroup.py
+++ b/gradio/components/checkboxgroup.py
@@ -35,7 +35,7 @@ class CheckboxGroup(
 
     def __init__(
         self,
-        choices: list[str | float | int] | None = None,
+        choices: list[str | int | float | tuple[str, str | int | float]] | None = None,
         *,
         value: list[str | float | int] | str | float | int | Callable | None = None,
         type: Literal["value", "index"] = "value",
@@ -54,22 +54,26 @@ def __init__(
     ):
         """
         Parameters:
-            choices: list of (string or numeric) options to select from.
-            value: default selected list of options. If a single choice is selected, it can be passed in as a string or numeric type. If callable, the function will be called whenever the app loads to set the initial value of the component.
+            choices: A list of string or numeric options to select from. An option can also be a tuple of the form (name, value), where name is the displayed name of the checkbox button and value is the value to be passed to the function, or returned by the function.
+            value: Default selected list of options. If a single choice is selected, it can be passed in as a string or numeric type. If callable, the function will be called whenever the app loads to set the initial value of the component.
             type: Type of value to be returned by component. "value" returns the list of strings of the choices selected, "index" returns the list of indices of the choices selected.
-            label: component name in interface.
-            info: additional component description.
+            label: Component name in interface.
+            info: Additional component description.
             every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute.
-            show_label: if True, will display label.
+            show_label: If True, will display label.
             container: If True, will place the component in a container - providing some extra padding around the border.
-            scale: relative width compared to adjacent Components in a Row. For example, if Component A has scale=2, and Component B has scale=1, A will be twice as wide as B. Should be an integer.
-            min_width: minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.
-            interactive: if True, choices in this checkbox group will be checkable; if False, checking will be disabled. If not provided, this is inferred based on whether the component is used as an input or output.
+            scale: Relative width compared to adjacent Components in a Row. For example, if Component A has scale=2, and Component B has scale=1, A will be twice as wide as B. Should be an integer.
+            min_width: Minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.
+            interactive: If True, choices in this checkbox group will be checkable; if False, checking will be disabled. If not provided, this is inferred based on whether the component is used as an input or output.
             visible: If False, component will be hidden.
             elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
             elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.
         """
-        self.choices = choices or []
+        self.choices = (
+            [c if isinstance(c, tuple) else (str(c), c) for c in choices]
+            if choices
+            else []
+        )
         valid_types = ["value", "index"]
         if type not in valid_types:
             raise ValueError(
@@ -109,8 +113,8 @@ def get_config(self):
 
     def example_inputs(self) -> dict[str, Any]:
         return {
-            "raw": self.choices[0] if self.choices else None,
-            "serialized": self.choices[0] if self.choices else None,
+            "raw": [self.choices[0][1]] if self.choices else None,
+            "serialized": [self.choices[0][1]] if self.choices else None,
         }
 
     @staticmethod
@@ -119,7 +123,7 @@ def update(
         | str
         | Literal[_Keywords.NO_VALUE]
         | None = _Keywords.NO_VALUE,
-        choices: list[str] | None = None,
+        choices: list[str | int | float | tuple[str, str | int | float]] | None = None,
         label: str | None = None,
         info: str | None = None,
         show_label: bool | None = None,
@@ -148,12 +152,12 @@ def preprocess(self, x: list[str | int | float]) -> list[str | int | float]:
         Parameters:
             x: list of selected choices
         Returns:
-            list of selected choices as strings or indices within choice list
+            list of selected choice values as strings or indices within choice list
         """
         if self.type == "value":
             return x
         elif self.type == "index":
-            return [self.choices.index(choice) for choice in x]
+            return [[value for _, value in self.choices].index(choice) for choice in x]
         else:
             raise ValueError(
                 f"Unknown type: {self.type}. Please choose from: 'value', 'index'."
@@ -163,9 +167,8 @@ def postprocess(
         self, y: list[str | int | float] | str | int | float | None
     ) -> list[str | int | float]:
         """
-        Any postprocessing needed to be performed on function output.
         Parameters:
-            y: List of selected choices. If a single choice is selected, it can be passed in as a string
+            y: List of selected choice values. If a single choice is selected, it can be passed in as a string
         Returns:
             List of selected choices
         """
@@ -177,7 +180,7 @@ def postprocess(
 
     def get_interpretation_neighbors(self, x):
         leave_one_out_sets = []
-        for choice in self.choices:
+        for choice in [value for _, value in self.choices]:
             leave_one_out_set = list(x)
             if choice in leave_one_out_set:
                 leave_one_out_set.remove(choice)
@@ -192,7 +195,7 @@ def get_interpretation_scores(self, x, neighbors, scores, **kwargs):
             For each tuple in the list, the first value represents the interpretation score if the input is False, and the second if the input is True.
         """
         final_scores = []
-        for choice, score in zip(self.choices, scores):
+        for choice, score in zip([value for _, value in self.choices], scores):
             score_set = [score, None] if choice in x else [None, score]
             final_scores.append(score_set)
         return final_scores
@@ -213,3 +216,13 @@ def style(
         if container is not None:
             self.container = container
         return self
+
+    def as_example(self, input_data):
+        if input_data is None:
+            return None
+        elif not isinstance(input_data, list):
+            input_data = [input_data]
+        return [
+            next((c[0] for c in self.choices if c[1] == data), None)
+            for data in input_data
+        ]
diff --git a/gradio/components/radio.py b/gradio/components/radio.py
--- a/gradio/components/radio.py
+++ b/gradio/components/radio.py
@@ -36,7 +36,7 @@ class Radio(
 
     def __init__(
         self,
-        choices: list[str | int | float] | None = None,
+        choices: list[str | int | float | tuple[str, str | int | float]] | None = None,
         *,
         value: str | int | float | Callable | None = None,
         type: str = "value",
@@ -55,22 +55,26 @@ def __init__(
     ):
         """
         Parameters:
-            choices: list of options to select from.
-            value: the button selected by default. If None, no button is selected by default. If callable, the function will be called whenever the app loads to set the initial value of the component.
+            choices: A list of string or numeric options to select from. An option can also be a tuple of the form (name, value), where name is the displayed name of the radio button and value is the value to be passed to the function, or returned by the function.
+            value: The option selected by default. If None, no option is selected by default. If callable, the function will be called whenever the app loads to set the initial value of the component.
             type: Type of value to be returned by component. "value" returns the string of the choice selected, "index" returns the index of the choice selected.
-            label: component name in interface.
-            info: additional component description.
+            label: Component name in interface.
+            info: Additional component description.
             every: If `value` is a callable, run the function 'every' number of seconds while the client connection is open. Has no effect otherwise. Queue must be enabled. The event can be accessed (e.g. to cancel it) via this component's .load_event attribute.
             show_label: if True, will display label.
             container: If True, will place the component in a container - providing some extra padding around the border.
-            scale: relative width compared to adjacent Components in a Row. For example, if Component A has scale=2, and Component B has scale=1, A will be twice as wide as B. Should be an integer.
-            min_width: minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.
-            interactive: if True, choices in this radio group will be selectable; if False, selection will be disabled. If not provided, this is inferred based on whether the component is used as an input or output.
+            scale: Relative width compared to adjacent Components in a Row. For example, if Component A has scale=2, and Component B has scale=1, A will be twice as wide as B. Should be an integer.
+            min_width: Minimum pixel width, will wrap if not sufficient screen space to satisfy this value. If a certain scale value results in this Component being narrower than min_width, the min_width parameter will be respected first.
+            interactive: If True, choices in this radio group will be selectable; if False, selection will be disabled. If not provided, this is inferred based on whether the component is used as an input or output.
             visible: If False, component will be hidden.
             elem_id: An optional string that is assigned as the id of this component in the HTML DOM. Can be used for targeting CSS styles.
             elem_classes: An optional list of strings that are assigned as the classes of this component in the HTML DOM. Can be used for targeting CSS styles.
         """
-        self.choices = choices or []
+        self.choices = (
+            [c if isinstance(c, tuple) else (str(c), c) for c in choices]
+            if choices
+            else []
+        )
         valid_types = ["value", "index"]
         if type not in valid_types:
             raise ValueError(
@@ -110,8 +114,8 @@ def get_config(self):
 
     def example_inputs(self) -> dict[str, Any]:
         return {
-            "raw": self.choices[0] if self.choices else None,
-            "serialized": self.choices[0] if self.choices else None,
+            "raw": self.choices[0][1] if self.choices else None,
+            "serialized": self.choices[0][1] if self.choices else None,
         }
 
     @staticmethod
@@ -121,7 +125,7 @@ def update(
         | float
         | Literal[_Keywords.NO_VALUE]
         | None = _Keywords.NO_VALUE,
-        choices: list[str | int | float] | None = None,
+        choices: list[str | int | float | tuple[str, str | int | float]] | None = None,
         label: str | None = None,
         info: str | None = None,
         show_label: bool | None = None,
@@ -150,7 +154,7 @@ def preprocess(self, x: str | int | float | None) -> str | int | float | None:
         Parameters:
             x: selected choice
         Returns:
-            selected choice as string or index within choice list
+            value of the selected choice as string or index within choice list
         """
         if self.type == "value":
             return x
@@ -158,14 +162,14 @@ def preprocess(self, x: str | int | float | None) -> str | int | float | None:
             if x is None:
                 return None
             else:
-                return self.choices.index(x)
+                return [value for _, value in self.choices].index(x)
         else:
             raise ValueError(
                 f"Unknown type: {self.type}. Please choose from: 'value', 'index'."
             )
 
     def get_interpretation_neighbors(self, x):
-        choices = list(self.choices)
+        choices = [value for _, value in self.choices]
         choices.remove(x)
         return choices, {}
 
@@ -176,7 +180,8 @@ def get_interpretation_scores(
         Returns:
             Each value represents the interpretation score corresponding to each choice.
         """
-        scores.insert(self.choices.index(x), None)
+        choices = [value for _, value in self.choices]
+        scores.insert(choices.index(x), None)
         return scores
 
     def style(
@@ -195,3 +200,6 @@ def style(
         if container is not None:
             self.container = container
         return self
+
+    def as_example(self, input_data):
+        return next((c[0] for c in self.choices if c[1] == input_data), None)
diff --git a/gradio/utils.py b/gradio/utils.py
--- a/gradio/utils.py
+++ b/gradio/utils.py
@@ -10,6 +10,7 @@
 import json.decoder
 import os
 import pkgutil
+import pprint
 import random
 import re
 import time
@@ -175,6 +176,7 @@ def assert_configs_are_equivalent_besides_ids(
     """
     config1 = copy.deepcopy(config1)
     config2 = copy.deepcopy(config2)
+    pp = pprint.PrettyPrinter(indent=2)
 
     for key in root_keys:
         assert config1[key] == config2[key], f"Configs have different: {key}"
@@ -190,7 +192,7 @@ def assert_same_components(config1_id, config2_id):
         c1.pop("id")
         c2 = copy.deepcopy(c2)
         c2.pop("id")
-        assert c1 == c2, f"{c1} does not match {c2}"
+        assert c1 == c2, f"{pp.pprint(c1)} does not match {pp.pprint(c2)}"
 
     def same_children_recursive(children1, chidren2):
         for child1, child2 in zip(children1, chidren2):


Test patch:
diff --git a/gradio/test_data/blocks_configs.py b/gradio/test_data/blocks_configs.py
--- a/gradio/test_data/blocks_configs.py
+++ b/gradio/test_data/blocks_configs.py
@@ -21,7 +21,11 @@
             "id": 2,
             "type": "checkboxgroup",
             "props": {
-                "choices": ["Covid", "Malaria", "Lung Cancer"],
+                "choices": [
+                    ("Covid", "Covid"),
+                    ("Malaria", "Malaria"),
+                    ("Lung Cancer", "Lung Cancer"),
+                ],
                 "value": [],
                 "label": "Disease to Scan For",
                 "show_label": True,
@@ -35,7 +39,7 @@
                 "info": {"type": "array", "items": {"type": "string"}},
                 "serialized_info": False,
             },
-            "example_inputs": {"raw": "Covid", "serialized": "Covid"},
+            "example_inputs": {"raw": ["Covid"], "serialized": ["Covid"]},
         },
         {"id": 3, "type": "tabs", "props": {"visible": True}},
         {"id": 4, "type": "tabitem", "props": {"label": "X-ray", "visible": True}},
@@ -346,7 +350,11 @@
             "id": 7,
             "type": "checkboxgroup",
             "props": {
-                "choices": ["Covid", "Malaria", "Lung Cancer"],
+                "choices": [
+                    ("Covid", "Covid"),
+                    ("Malaria", "Malaria"),
+                    ("Lung Cancer", "Lung Cancer"),
+                ],
                 "value": [],
                 "label": "Disease to Scan For",
                 "show_label": True,
@@ -360,7 +368,7 @@
                 "info": {"type": "array", "items": {"type": "string"}},
                 "serialized_info": False,
             },
-            "example_inputs": {"raw": "Covid", "serialized": "Covid"},
+            "example_inputs": {"raw": ["Covid"], "serialized": ["Covid"]},
         },
         {"id": 8, "type": "tabs", "props": {"visible": True}},
         {"id": 9, "type": "tabitem", "props": {"label": "X-ray", "visible": True}},
@@ -668,7 +676,11 @@
             "id": 2,
             "type": "checkboxgroup",
             "props": {
-                "choices": ["Covid", "Malaria", "Lung Cancer"],
+                "choices": [
+                    ("Covid", "Covid"),
+                    ("Malaria", "Malaria"),
+                    ("Lung Cancer", "Lung Cancer"),
+                ],
                 "value": [],
                 "name": "checkboxgroup",
                 "show_label": True,
diff --git a/js/radio/Radio.test.ts b/js/radio/Radio.test.ts
--- a/js/radio/Radio.test.ts
+++ b/js/radio/Radio.test.ts
@@ -19,7 +19,11 @@ const loading_status = {
 
 describe("Radio", () => {
 	afterEach(() => cleanup());
-	const choices = ["dog", "cat", "turtle"];
+	const choices = [
+		["dog", "dog"],
+		["cat", "cat"],
+		["turtle", "turtle"]
+	];
 
 	test("renders provided value", async () => {
 		const { getAllByRole, getByTestId } = await render(Radio, {
@@ -31,17 +35,16 @@ describe("Radio", () => {
 			mode: "dynamic"
 		});
 
-		const radioButtons: HTMLOptionElement[] = getAllByRole("radio");
-
 		assert.equal(
 			getByTestId("cat-radio-label").className.includes("selected"),
 			true
 		);
 
+		const radioButtons: HTMLOptionElement[] = getAllByRole("radio");
 		assert.equal(radioButtons.length, 3);
 
 		radioButtons.forEach((radioButton: HTMLOptionElement, index) => {
-			assert.equal(radioButton.value === choices[index], true);
+			assert.equal(radioButton.value === choices[index][1], true);
 		});
 	});
 
diff --git a/test/test_components.py b/test/test_components.py
--- a/test/test_components.py
+++ b/test/test_components.py
@@ -502,7 +502,7 @@ def test_component_functions(self):
             label="Check Your Inputs",
         )
         assert checkboxes_input.get_config() == {
-            "choices": ["a", "b", "c"],
+            "choices": [("a", "a"), ("b", "b"), ("c", "c")],
             "value": ["a", "c"],
             "name": "checkboxgroup",
             "show_label": True,
@@ -548,7 +548,7 @@ def test_component_functions(self):
             choices=["a", "b", "c"], default="a", label="Pick Your One Input"
         )
         assert radio_input.get_config() == {
-            "choices": ["a", "b", "c"],
+            "choices": [("a", "a"), ("b", "b"), ("c", "c")],
             "value": None,
             "name": "radio",
             "show_label": True,
@@ -697,7 +697,6 @@ def test_component_functions(self):
         with pytest.raises(ValueError):
             gr.Image(type="unknown")
         image_input.shape = (30, 10)
-        assert image_input._segment_by_slic(img) is not None
 
         # Output functionalities
         y_img = gr.processing_utils.decode_base64_to_image(
@@ -2036,9 +2035,9 @@ async def test_in_interface(self):
 
         def get_avg_age_per_gender(data):
             return {
-                "M": int(data[data["gender"] == "M"].mean()),
-                "F": int(data[data["gender"] == "F"].mean()),
-                "O": int(data[data["gender"] == "O"].mean()),
+                "M": int(data[data["gender"] == "M"]["age"].mean()),
+                "F": int(data[data["gender"] == "F"]["age"].mean()),
+                "O": int(data[data["gender"] == "O"]["age"].mean()),
             }
 
         iface = gr.Interface(
@@ -2375,13 +2374,6 @@ def test_no_color(self):
         assert config["encoding"]["x"]["field"] == "Horsepower"
         assert config["encoding"]["x"]["title"] == "Horse"
         assert config["encoding"]["y"]["field"] == "Miles_per_Gallon"
-        assert config["selection"] == {
-            "selector001": {
-                "bind": "scales",
-                "encodings": ["x", "y"],
-                "type": "interval",
-            }
-        }
         assert config["title"] == "Car Data"
         assert "height" not in config
         assert "width" not in config