Repository: streamlit/streamlit

Issue:
Support NumPy/Pandas types for st.slider values
### Problem

When trying to use a Pandas (version 2.0.2) DataFrame's values to set a slider's range, the `st.slider` data type validation rejects it since it's not a plain Python `int`. For example, here is a minimal example to reproduce the error:

```python
import streamlit as st
import pandas as pd

df = pd.DataFrame({"a": [1, 2, 3]})

st.slider(
    "foo",
    min_value=df["a"].min(),
    max_value=df["a"].max(),
)
```

if run, this gives the error:

```
streamlit.errors.StreamlitAPIException: Slider value should either be an int/float/datetime or a list/tuple of 0 to 2 ints/floats/datetimes
```

This is because in Pandas 2, `type(df["a"].min())` gives `<class 'numpy.int64'>` (but I think it gave `int` in Pandas 1).

This will generally the case any time we get our values from a NumPy or pandas object.

### Solution

**MVP:** Add `np.int64: SliderProto.INT,` to `SUPPORTED_TYPES` in `lib/streamlit/elemets/lib/slider.py`. However, this would need us to add all NumPy types explicitly.

**Better solution:** Use `numbers` module to check types more generically. I have a potential PR incoming.

---

Community voting on feature requests enables the Streamlit team to understand which features are most important to our users.

**If you'd like the Streamlit team to prioritize this feature request, please use the 👍 (thumbs up emoji) reaction in response to the initial post.**




Discussion:

Resolving patch:
diff --git a/lib/streamlit/elements/slider.py b/lib/streamlit/elements/slider.py
--- a/lib/streamlit/elements/slider.py
+++ b/lib/streamlit/elements/slider.py
@@ -14,6 +14,7 @@
 
 from dataclasses import dataclass
 from datetime import date, datetime, time, timedelta, timezone, tzinfo
+from numbers import Integral, Real
 from textwrap import dedent
 from typing import (
     TYPE_CHECKING,
@@ -389,8 +390,8 @@ def _slider(
                 value = min_value if min_value is not None else 0
 
         SUPPORTED_TYPES = {
-            int: SliderProto.INT,
-            float: SliderProto.FLOAT,
+            Integral: SliderProto.INT,
+            Real: SliderProto.FLOAT,
             datetime: SliderProto.DATETIME,
             date: SliderProto.DATE,
             time: SliderProto.TIME,
@@ -410,8 +411,16 @@ def _slider(
         if single_value:
             value = [value]
 
+        def value_to_generic_type(v):
+            if isinstance(v, Integral):
+                return SUPPORTED_TYPES[Integral]
+            elif isinstance(v, Real):
+                return SUPPORTED_TYPES[Real]
+            else:
+                return SUPPORTED_TYPES[type(v)]
+
         def all_same_type(items):
-            return len(set(map(type, items))) < 2
+            return len(set(map(value_to_generic_type, items))) < 2
 
         if not all_same_type(value):
             raise StreamlitAPIException(
@@ -422,7 +431,7 @@ def all_same_type(items):
         if len(value) == 0:
             data_type = SliderProto.INT
         else:
-            data_type = SUPPORTED_TYPES[type(value[0])]
+            data_type = value_to_generic_type(value[0])
 
         datetime_min = time.min
         datetime_max = time.max
@@ -487,8 +496,13 @@ def all_same_type(items):
 
         # Ensure that all arguments are of the same type.
         slider_args = [min_value, max_value, step]
-        int_args = all(map(lambda a: isinstance(a, int), slider_args))
-        float_args = all(map(lambda a: isinstance(a, float), slider_args))
+        int_args = all(map(lambda a: isinstance(a, Integral), slider_args))
+        float_args = all(
+            map(
+                lambda a: isinstance(a, Real) and not isinstance(a, Integral),
+                slider_args,
+            )
+        )
         # When min and max_value are the same timelike, step should be a timedelta
         timelike_args = (
             data_type in TIMELIKE_TYPES


Test patch:
diff --git a/lib/tests/streamlit/elements/slider_test.py b/lib/tests/streamlit/elements/slider_test.py
--- a/lib/tests/streamlit/elements/slider_test.py
+++ b/lib/tests/streamlit/elements/slider_test.py
@@ -17,6 +17,7 @@
 from datetime import date, datetime, time, timedelta, timezone
 from unittest.mock import MagicMock, patch
 
+import numpy as np
 import pytest
 from parameterized import parameterized
 
@@ -69,6 +70,10 @@ def test_just_disabled(self):
             (0.5, [0.5], 0.5),  # float
             ((0.2, 0.5), [0.2, 0.5], (0.2, 0.5)),  # float tuple
             ([0.2, 0.5], [0.2, 0.5], (0.2, 0.5)),  # float list
+            (np.int64(1), [1], 1),  # numpy int
+            (np.int32(1), [1], 1),  # numpy int
+            (np.single(0.5), [0.5], 0.5),  # numpy float
+            (np.double(0.5), [0.5], 0.5),  # numpy float
             (AWARE_DT, [AWARE_DT_MICROS], AWARE_DT),  # datetime
             (
                 (AWARE_DT, AWARE_DT_END),  # datetime tuple
@@ -103,6 +108,34 @@ def test_value_types(self, value, proto_value, return_value):
         self.assertEqual(c.label, "the label")
         self.assertEqual(c.default, proto_value)
 
+    @parameterized.expand(
+        [
+            "5",  # str
+            5j,  # complex
+            b"5",  # bytes
+        ]
+    )
+    def test_invalid_types(self, value):
+        """Test that it rejects invalid types, specifically things that are *almost* numbers"""
+        with pytest.raises(StreamlitAPIException):
+            st.slider("the label", value=value)
+
+    @parameterized.expand(
+        [
+            (1, 1, 1, 1),
+            (np.int64(1), 1, 1, 1),
+            (1, np.int64(1), 1, 1),
+            (1, 1, np.int64(1), 1),
+            (np.single(0.5), 0.5, 0.5, 0.5),
+        ]
+    )
+    def test_matching_types(self, min_value, max_value, value, return_value):
+        """Test that NumPy types are seen as compatible with numerical Python types"""
+        ret = st.slider(
+            "the label", min_value=min_value, max_value=max_value, value=value
+        )
+        self.assertEqual(ret, return_value)
+
     NAIVE_DT = datetime(2020, 2, 1)
     NAIVE_DT_END = datetime(2020, 2, 4)
     NAIVE_TIME = time(6, 20, 34)