Skip to content

Error when first element of a plotted series is NA #1021

@petrelharp

Description

@petrelharp

Consider the following example:

import pandas as pd
(
    pd.DataFrame({'x': [0,1,2,3], 'y': pd.Series([0,pd.NA,2,3], dtype="Int64")})
    >>
    ggplot(aes(x='x', y='y')) + geom_line()
)

This correctly produces a plot (after removing the missing value). However, if the NA in y is moved to the front:

import pandas as pd
(
    pd.DataFrame({'x': [0,1,2,3], 'y': pd.Series([pd.NA,1,2,3], dtype="Int64")})
    >>
    ggplot(aes(x='x', y='y')) + geom_line()
)

we get (with plotnine 0.15.2 and pandas 2.3.3) the following:

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File [~/micromamba/envs/ds435/lib/python3.14/site-packages/IPython/core/formatters.py:1036](http://localhost:8888/home/peter/micromamba/envs/ds435/lib/python3.14/site-packages/IPython/core/formatters.py#line=1035), in MimeBundleFormatter.__call__(self, obj, include, exclude)
   1033     method = get_real_method(obj, self.print_method)
   1035     if method is not None:
-> 1036         return method(include=include, exclude=exclude)
   1037     return None
   1038 else:

File [~/micromamba/envs/ds435/lib/python3.14/site-packages/plotnine/ggplot.py:172](http://localhost:8888/home/peter/micromamba/envs/ds435/lib/python3.14/site-packages/plotnine/ggplot.py#line=171), in ggplot._repr_mimebundle_(self, include, exclude)
    169     self.theme = self.theme.to_retina()
    171 buf = BytesIO()
--> 172 self.save(buf, "png" if format == "retina" else format, verbose=False)
    173 figure_size_px = self.theme._figure_size_px
    174 return get_mimebundle(buf.getvalue(), format, figure_size_px)

File [~/micromamba/envs/ds435/lib/python3.14/site-packages/plotnine/ggplot.py:681](http://localhost:8888/home/peter/micromamba/envs/ds435/lib/python3.14/site-packages/plotnine/ggplot.py#line=680), in ggplot.save(self, filename, format, path, width, height, units, dpi, limitsize, verbose, **kwargs)
    632 def save(
    633     self,
    634     filename: Optional[str | Path | BytesIO] = None,
   (...)    643     **kwargs: Any,
    644 ):
    645     """
    646     Save a ggplot object as an image file
    647 
   (...)    679         Additional arguments to pass to matplotlib `savefig()`.
    680     """
--> 681     sv = self.save_helper(
    682         filename=filename,
    683         format=format,
    684         path=path,
    685         width=width,
    686         height=height,
    687         units=units,
    688         dpi=dpi,
    689         limitsize=limitsize,
    690         verbose=verbose,
    691         **kwargs,
    692     )
    694     with plot_context(self).rc_context:
    695         sv.figure.savefig(**sv.kwargs)

File [~/micromamba/envs/ds435/lib/python3.14/site-packages/plotnine/ggplot.py:629](http://localhost:8888/home/peter/micromamba/envs/ds435/lib/python3.14/site-packages/plotnine/ggplot.py#line=628), in ggplot.save_helper(self, filename, format, path, width, height, units, dpi, limitsize, verbose, **kwargs)
    626 if dpi is not None:
    627     self.theme = self.theme + theme(dpi=dpi)
--> 629 figure = self.draw(show=False)
    630 return mpl_save_view(figure, fig_kwargs)

File [~/micromamba/envs/ds435/lib/python3.14/site-packages/plotnine/ggplot.py:306](http://localhost:8888/home/peter/micromamba/envs/ds435/lib/python3.14/site-packages/plotnine/ggplot.py#line=305), in ggplot.draw(self, show)
    304 with plot_context(self, show=show):
    305     figure = self._setup()
--> 306     self._build()
    308     # setup
    309     self.axs = self.facet.setup(self)

File [~/micromamba/envs/ds435/lib/python3.14/site-packages/plotnine/ggplot.py:390](http://localhost:8888/home/peter/micromamba/envs/ds435/lib/python3.14/site-packages/plotnine/ggplot.py#line=389), in ggplot._build(self)
    387 # Map and train positions so that statistics have access
    388 # to ranges and all positions are numeric
    389 layout.train_position(layers, scales)
--> 390 layout.map_position(layers)
    392 # Apply and map statistics
    393 layers.compute_statistic(layout)

File [~/micromamba/envs/ds435/lib/python3.14/site-packages/plotnine/facets/layout.py:136](http://localhost:8888/home/peter/micromamba/envs/ds435/lib/python3.14/site-packages/plotnine/facets/layout.py#line=135), in Layout.map_position(self, layers)
    132 y_vars = list(
    133     set(self.panel_scales_y[0].aesthetics) & set(data.columns)
    134 )
    135 SCALE_Y = _layout["SCALE_Y"].iloc[match_id].tolist()
--> 136 self.panel_scales_y.map(data, y_vars, SCALE_Y)

File [~/micromamba/envs/ds435/lib/python3.14/site-packages/plotnine/scales/scales.py:178](http://localhost:8888/home/peter/micromamba/envs/ds435/lib/python3.14/site-packages/plotnine/scales/scales.py#line=177), in Scales.map(self, data, vars, idx)
    176 for i, sc in enumerate(self, start=1):
    177     bool_idx = i == idx
--> 178     results = sc.map(data.loc[bool_idx, col])
    179     if use_df:
    180         discrete_data.loc[bool_idx, col] = results

File [~/micromamba/envs/ds435/lib/python3.14/site-packages/plotnine/scales/scale_xy.py:205](http://localhost:8888/home/peter/micromamba/envs/ds435/lib/python3.14/site-packages/plotnine/scales/scale_xy.py#line=204), in scale_position_continuous.map(self, x, limits)
    203 if limits is None:
    204     limits = self.final_limits
--> 205 scaled = self.oob(x, limits)  # type: ignore
    206 scaled[pd.isna(scaled)] = self.na_value
    207 return scaled

File [~/micromamba/envs/ds435/lib/python3.14/site-packages/mizani/bounds.py:351](http://localhost:8888/home/peter/micromamba/envs/ds435/lib/python3.14/site-packages/mizani/bounds.py#line=350), in censor(x, range, only_finite)
    348 if not len(x):
    349     return res
--> 351 null = get_null_value(x)
    353 if only_finite:
    354     try:

File [~/micromamba/envs/ds435/lib/python3.14/site-packages/mizani/utils.py:359](http://localhost:8888/home/peter/micromamba/envs/ds435/lib/python3.14/site-packages/mizani/utils.py#line=358), in get_null_value(x)
    357     return type(x0)("NaT")
    358 else:
--> 359     raise ValueError(
    360         "Cannot get a null value for type: {}".format(type(x[0]))
    361     )

ValueError: Cannot get a null value for type: <class 'pandas._libs.missing.NAType'>

The error does not occur if we don't specify the nullable dtype "Int64".

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions