@@ -993,7 +993,7 @@ def _flox_reduce(
993993 dim : Dims ,
994994 keep_attrs : bool | None = None ,
995995 ** kwargs : Any ,
996- ):
996+ ) -> T_Xarray :
997997 """Adaptor function that translates our groupby API to that of flox."""
998998 import flox
999999 from flox .xarray import xarray_reduce
@@ -1116,6 +1116,8 @@ def _flox_reduce(
11161116 # flox always assigns an index so we must drop it here if we don't need it.
11171117 to_drop .append (grouper .name )
11181118 continue
1119+ # TODO: We can't simply use `self.encoded.coords` here because it corresponds to `unique_coord`,
1120+ # NOT `full_index`. We would need to construct a new Coordinates object, that corresponds to `full_index`.
11191121 new_coords .append (
11201122 # Using IndexVariable here ensures we reconstruct PandasMultiIndex with
11211123 # all associated levels properly.
@@ -1361,7 +1363,12 @@ def where(self, cond, other=dtypes.NA) -> T_Xarray:
13611363 """
13621364 return ops .where_method (self , cond , other )
13631365
1364- def _first_or_last (self , op , skipna , keep_attrs ):
1366+ def _first_or_last (
1367+ self ,
1368+ op : Literal ["first" | "last" ],
1369+ skipna : bool | None ,
1370+ keep_attrs : bool | None ,
1371+ ):
13651372 if all (
13661373 isinstance (maybe_slice , slice )
13671374 and (maybe_slice .stop == maybe_slice .start + 1 )
@@ -1372,17 +1379,65 @@ def _first_or_last(self, op, skipna, keep_attrs):
13721379 return self ._obj
13731380 if keep_attrs is None :
13741381 keep_attrs = _get_keep_attrs (default = True )
1375- return self .reduce (
1376- op , dim = [self ._group_dim ], skipna = skipna , keep_attrs = keep_attrs
1377- )
1382+ if (
1383+ module_available ("flox" , minversion = "0.10.0" )
1384+ and OPTIONS ["use_flox" ]
1385+ and contains_only_chunked_or_numpy (self ._obj )
1386+ ):
1387+ result = self ._flox_reduce (
1388+ dim = None , func = op , skipna = skipna , keep_attrs = keep_attrs
1389+ )
1390+ else :
1391+ result = self .reduce (
1392+ getattr (duck_array_ops , op ),
1393+ dim = [self ._group_dim ],
1394+ skipna = skipna ,
1395+ keep_attrs = keep_attrs ,
1396+ )
1397+ return result
13781398
1379- def first (self , skipna : bool | None = None , keep_attrs : bool | None = None ):
1380- """Return the first element of each group along the group dimension"""
1381- return self ._first_or_last (duck_array_ops .first , skipna , keep_attrs )
1399+ def first (
1400+ self , skipna : bool | None = None , keep_attrs : bool | None = None
1401+ ) -> T_Xarray :
1402+ """
1403+ Return the first element of each group along the group dimension
13821404
1383- def last (self , skipna : bool | None = None , keep_attrs : bool | None = None ):
1384- """Return the last element of each group along the group dimension"""
1385- return self ._first_or_last (duck_array_ops .last , skipna , keep_attrs )
1405+ Parameters
1406+ ----------
1407+ skipna : bool or None, optional
1408+ If True, skip missing values (as marked by NaN). By default, only
1409+ skips missing values for float dtypes; other dtypes either do not
1410+ have a sentinel missing value (int) or ``skipna=True`` has not been
1411+ implemented (object, datetime64 or timedelta64).
1412+ keep_attrs : bool or None, optional
1413+ If True, ``attrs`` will be copied from the original
1414+ object to the new one. If False, the new object will be
1415+ returned without attributes.
1416+
1417+ """
1418+ return self ._first_or_last ("first" , skipna , keep_attrs )
1419+
1420+ def last (
1421+ self , skipna : bool | None = None , keep_attrs : bool | None = None
1422+ ) -> T_Xarray :
1423+ """
1424+ Return the last element of each group along the group dimension
1425+
1426+ Parameters
1427+ ----------
1428+ skipna : bool or None, optional
1429+ If True, skip missing values (as marked by NaN). By default, only
1430+ skips missing values for float dtypes; other dtypes either do not
1431+ have a sentinel missing value (int) or ``skipna=True`` has not been
1432+ implemented (object, datetime64 or timedelta64).
1433+ keep_attrs : bool or None, optional
1434+ If True, ``attrs`` will be copied from the original
1435+ object to the new one. If False, the new object will be
1436+ returned without attributes.
1437+
1438+
1439+ """
1440+ return self ._first_or_last ("last" , skipna , keep_attrs )
13861441
13871442 def assign_coords (self , coords = None , ** coords_kwargs ):
13881443 """Assign coordinates by group.
0 commit comments