@@ -264,6 +264,7 @@ class GroupBy:
264
264
"_stacked_dim" ,
265
265
"_unique_coord" ,
266
266
"_dims" ,
267
+ "_bins" ,
267
268
)
268
269
269
270
def __init__ (
@@ -401,6 +402,7 @@ def __init__(
401
402
self ._inserted_dims = inserted_dims
402
403
self ._full_index = full_index
403
404
self ._restore_coord_dims = restore_coord_dims
405
+ self ._bins = bins
404
406
405
407
# cached attributes
406
408
self ._groups = None
@@ -478,35 +480,75 @@ def _infer_concat_args(self, applied_example):
478
480
return coord , dim , positions
479
481
480
482
def _binary_op (self , other , f , reflexive = False ):
483
+ from .dataarray import DataArray
484
+ from .dataset import Dataset
485
+
481
486
g = f if not reflexive else lambda x , y : f (y , x )
482
- applied = self ._yield_binary_applied (g , other )
483
- return self ._combine (applied )
484
487
485
- def _yield_binary_applied (self , func , other ):
486
- dummy = None
488
+ obj = self ._obj
489
+ group = self ._group
490
+ dim = self ._group_dim
491
+ if isinstance (group , _DummyGroup ):
492
+ group = obj [dim ]
493
+ name = group .name
494
+
495
+ if not isinstance (other , (Dataset , DataArray )):
496
+ raise TypeError (
497
+ "GroupBy objects only support binary ops "
498
+ "when the other argument is a Dataset or "
499
+ "DataArray"
500
+ )
487
501
488
- for group_value , obj in self :
489
- try :
490
- other_sel = other .sel (** {self ._group .name : group_value })
491
- except AttributeError :
492
- raise TypeError (
493
- "GroupBy objects only support binary ops "
494
- "when the other argument is a Dataset or "
495
- "DataArray"
496
- )
497
- except (KeyError , ValueError ):
498
- if self ._group .name not in other .dims :
499
- raise ValueError (
500
- "incompatible dimensions for a grouped "
501
- f"binary operation: the group variable { self ._group .name !r} "
502
- "is not a dimension on the other argument"
502
+ if name not in other .dims :
503
+ raise ValueError (
504
+ "incompatible dimensions for a grouped "
505
+ f"binary operation: the group variable { name !r} "
506
+ "is not a dimension on the other argument"
507
+ )
508
+
509
+ try :
510
+ expanded = other .sel ({name : group })
511
+ except KeyError :
512
+ # some labels are absent i.e. other is not aligned
513
+ # so we align by reindexing and then rename dimensions.
514
+
515
+ # Broadcast out scalars for backwards compatibility
516
+ # TODO: get rid of this when fixing GH2145
517
+ for var in other .coords :
518
+ if other [var ].ndim == 0 :
519
+ other [var ] = (
520
+ other [var ].drop_vars (var ).expand_dims ({name : other .sizes [name ]})
503
521
)
504
- if dummy is None :
505
- dummy = _dummy_copy (other )
506
- other_sel = dummy
522
+ expanded = (
523
+ other .reindex ({name : group .data })
524
+ .rename ({name : dim })
525
+ .assign_coords ({dim : obj [dim ]})
526
+ )
507
527
508
- result = func (obj , other_sel )
509
- yield result
528
+ if self ._bins is not None and name == dim and dim not in obj .xindexes :
529
+ # When binning by unindexed coordinate we need to reindex obj.
530
+ # _full_index is IntervalIndex, so idx will be -1 where
531
+ # a value does not belong to any bin. Using IntervalIndex
532
+ # accounts for any non-default cut_kwargs passed to the constructor
533
+ idx = pd .cut (group , bins = self ._full_index ).codes
534
+ obj = obj .isel ({dim : np .arange (group .size )[idx != - 1 ]})
535
+
536
+ result = g (obj , expanded )
537
+
538
+ result = self ._maybe_unstack (result )
539
+ group = self ._maybe_unstack (group )
540
+ if group .ndim > 1 :
541
+ # backcompat:
542
+ # TODO: get rid of this when fixing GH2145
543
+ for var in set (obj .coords ) - set (obj .xindexes ):
544
+ if set (obj [var ].dims ) < set (group .dims ):
545
+ result [var ] = obj [var ].reset_coords (drop = True ).broadcast_like (group )
546
+
547
+ if isinstance (result , Dataset ) and isinstance (obj , Dataset ):
548
+ for var in set (result ):
549
+ if dim not in obj [var ].dims :
550
+ result [var ] = result [var ].transpose (dim , ...)
551
+ return result
510
552
511
553
def _maybe_restore_empty_groups (self , combined ):
512
554
"""Our index contained empty groups (e.g., from a resampling). If we
0 commit comments