# Fix dtype of pd.Series or pd.DataFrame to a specific type

**URL:** <https://discourse.holoviz.org/t/fix-dtype-of-pd-series-or-pd-dataframe-to-a-specific-type/6863>\
**Category:** Param\
**Created:** [February 13, 2024, 2:30pm UTC](https://discourse.holoviz.org/t/fix-dtype-of-pd-series-or-pd-dataframe-to-a-specific-type/6863 "2024-02-13T14:30:08Z")\
**Posts on this page:** 4\
**Page:** 1

<div class="post-metadata">

**Author:** ![bertcoerver](https://discourse.holoviz.org/user_avatar/discourse.holoviz.org/bertcoerver/32/6232_2.png) [@bertcoerver](https://discourse.holoviz.org/u/bertcoerver)\
**Post date:** [February 13, 2024, 2:30pm UTC](https://discourse.holoviz.org/t/fix-dtype-of-pd-series-or-pd-dataframe-to-a-specific-type/6863/1 "2024-02-13T14:30:08Z")

</div>

Hello,

Is there any way to fix the attribute of a `param`? As an example, lets say I create a `param.ClassSelector` called `x` which holds an empty `pd.Series` with `dtype = int`. Now I know that `x` will always be a `pd.Series` (otherwise `param` raises an error), but I want to be sure that the `dtype` of `x` is always an integer:

```auto
import pandas as pd
import param

class A(param.Parameterized):

    x = param.ClassSelector(default = pd.Series(dtype = int), class_ = pd.Series)

    def __init__ (self, **params):
        super(). __init__ (**params)

    def change_dtype(self):
        print(self.x.dtype)
        self.x = pd.Series([1.2])
        print(self.x.dtype)

a = A()

a.change_dtype()

>>> int64
>>> float64

```

So, how to make sure the `dtype` attribute of `x` cannot change?

---

<div class="post-metadata">

**Author:** ![ahuang11](https://discourse.holoviz.org/user_avatar/discourse.holoviz.org/ahuang11/32/4947_2.png) [@ahuang11](https://discourse.holoviz.org/u/ahuang11)\
**Post date:** [February 13, 2024, 3:32pm UTC](https://discourse.holoviz.org/t/fix-dtype-of-pd-series-or-pd-dataframe-to-a-specific-type/6863/2 "2024-02-13T15:32:06Z")

</div>

You’d probably have to inherit `param.Series` and write your own `_validate_value`  
[https://param.holoviz.org/\_modules/param/parameters.html#Series](https://param.holoviz.org/_modules/param/parameters.html#Series)

An example

> <https://github.com/holoviz/panel/blob/723768ef2c8966f9da437d8c4b07b79e67768e30/panel/_param.py#L18-L26>

---

<div class="post-metadata">

**Author:** ![bertcoerver](https://discourse.holoviz.org/user_avatar/discourse.holoviz.org/bertcoerver/32/6232_2.png) [@bertcoerver](https://discourse.holoviz.org/u/bertcoerver)\
**Post date:** [February 14, 2024, 9:46am UTC](https://discourse.holoviz.org/t/fix-dtype-of-pd-series-or-pd-dataframe-to-a-specific-type/6863/3 "2024-02-14T09:46:02Z")

</div>

Thanks @ahuang11 ! With your example and the explanation in the docs [over here](https://param.holoviz.org/user_guide/Parameters.html#specialized-parameter-types) I managed to make something for `param.DataFrame`.

With this class I can now give the `columns` a dictionary specifying the column names and the expected `dtypes` or pass a dictionary to `dtypes` directly:

```auto
class SuperDataFrame(param.DataFrame):

    __slots__ = ['dtypes']

    _slot_defaults = param._utils._dict_update(
        param.DataFrame._slot_defaults, dtypes = None
    )

    def __init__ (self, **params):

        self.dtypes = params.pop("dtypes", {})
        if isinstance(params.get("columns", None), dict):
            self.dtypes = params["columns"]
            params["columns"] = set(params["columns"].keys())

        super(). __init__ (**params)

    def _validate(self, val):

        super()._validate(val)

        invalid_dtypes = [(k, val.dtypes[k], v) for k, v in self.dtypes.items() if not val.dtypes[k] == v]
        if invalid_dtypes:
            errors = [f"column `{x[0]}` should be of type `{getattr(x[2], " __name__")}` but is `{x[1]}`." for x in invalid_dtypes]
            raise ValueError(
                f'{param._utils._validate_error_prefix(self)}: {" and, ".join(errors)} '
            )

class A(param.Parameterized):

    df = SuperDataFrame(
            default = pd.DataFrame({"int_data": [1,2,3], "float_data": [1.2, 3.4, 5.6]}),
            columns = {"int_data": int, "float_data": float},
        )

    def __init__ (self, **params):
        super(). __init__ (**params)

    def invalid_data_1(self):
        self.df = pd.DataFrame({"int_data": [1.1,2.1,3.1], "float_data": [1.2, 3.4, 5.6]})

    def invalid_data_2(self):
        self.df["int_data"] = pd.Series(["1","2","3"], dtype = str)
        # self.param.trigger("df")

a = A()

a.invalid_data_2()
a.invalid_data_1()

>>> ValueError: SuperDataFrame parameter 'A.df': column `int_data` should be of type `int` but is `float64`. 

```

I did notice however that the `_validate` method only gets called when I change the `df` entirely and NOT when I change only 1 column (as is done in `.invalid_data_2()`).

Is there any simple way to also trigger the check when a single columns gets changed, besides adding `self.param.trigger("df")` everywhere?

---

<div class="post-metadata">

**Author:** ![bertcoerver](https://discourse.holoviz.org/user_avatar/discourse.holoviz.org/bertcoerver/32/6232_2.png) [@bertcoerver](https://discourse.holoviz.org/u/bertcoerver)\
**Post date:** [February 14, 2024, 9:57am UTC](https://discourse.holoviz.org/t/fix-dtype-of-pd-series-or-pd-dataframe-to-a-specific-type/6863/4 "2024-02-14T09:57:35Z")

</div>

Think my follow up question is answered [here](https://discourse.holoviz.org/t/watch-a-dataframe/5108/2), and so `self.param.trigger("df")` is the way to go.
