# Buggy code
def broadcast_apply(cls, axis, apply_func, left, right):
    """Broadcast the right partitions to left and apply a function.

    Note: This will often be overridden by implementations. It materializes the
        entire partitions of the right and applies them to the left through `apply`.

    Args:
        axis: The axis to apply and broadcast over.
        apply_func: The function to apply.
        left: The left partitions.
        right: The right partitions.

    Returns:
        A new `np.array` of partition objects.
    """
    if right.shape == (1, 1):
        right_parts = right[0]
    else:
        right_parts = np.squeeze(right)

    [obj.drain_call_queue() for obj in right_parts]
    return np.array(
        [
            [
                part.add_to_apply_calls(
                    apply_func,
                    r=right_parts[col_idx].get()
                    if axis
                    else right_parts[row_idx].get(),
                )
                for col_idx, part in enumerate(left[row_idx])
            ]
            for row_idx in range(len(left))
        ]
    )


# Traceback (AttributeError)
import modin.pandas as pd
import glob

files = glob.glob('/path/to/files/*.parquet')
df = pd.concat([pd.read_parquet(file) for file in files])
df.reset_index(inplace=True)
df.drop(columns=['index', 'DATE', 'STATUS', 'ID', 'ID2],inplace=True)

df.groupby(['REPORTING_DATE', 'ENTITY', 'CURRENCY']).sum()

UserWarning: Multi-column groupby is a new feature. Please report any bugs/issues to bug_reports@modin.org.
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-15-081b1819b7d6> in <module>
----> 1 df.groupby(['DATA_SALDA', 'BANK', 'WALUTA_KON']).sum()

~/eksperyment/lib64/python3.6/site-packages/modin/pandas/groupby.py in sum(self, **kwargs)
323
324     def sum(self, **kwargs):
--> 325         return self._groupby_reduce(lambda df: df.sum(**kwargs), None)
326
327     def describe(self, **kwargs):

~/eksperyment/lib64/python3.6/site-packages/modin/pandas/groupby.py in _groupby_reduce(self, map_func, reduce_func, drop, numeric_only, **kwargs)
479                 reduce_args=kwargs,
480                 numeric_only=numeric_only,
--> 481                 drop=self._drop,
482             )
483         )

~/eksperyment/lib64/python3.6/site-packages/modin/backends/pandas/query_compiler.py in groupby_reduce(self, by, axis, groupby_args, map_func, map_args, reduce_func, reduce_args, numeric_only, drop)
1236             _reduce,
1237             new_columns=new_columns,
-> 1238             new_index=new_index,
1239         )
1240         result = self.__constructor__(new_modin_frame)

~/eksperyment/lib64/python3.6/site-packages/modin/engines/base/frame/data.py in groupby_reduce(self, axis, by, map_func, reduce_func, new_index, new_columns)
1148         """
1149         new_partitions = self._frame_mgr_cls.groupby_reduce(
-> 1150             axis, self._partitions, by._partitions, map_func, reduce_func
1151         )
1152         if new_columns is None:

~/eksperyment/lib64/python3.6/site-packages/modin/engines/base/frame/partition_manager.py in groupby_reduce(cls, axis, partitions, by, map_func, reduce_func)
66         if len(by_parts.shape) == 0:
67             by_parts = np.array([by_parts.item()])
---> 68         [obj.drain_call_queue() for obj in by_parts]
69         new_partitions = np.array(
70             [

~/eksperyment/lib64/python3.6/site-packages/modin/engines/base/frame/partition_manager.py in <listcomp>(.0)
66         if len(by_parts.shape) == 0:
67             by_parts = np.array([by_parts.item()])
---> 68         [obj.drain_call_queue() for obj in by_parts]
69         new_partitions = np.array(
70             [

AttributeError: 'numpy.ndarray' object has no attribute 'drain_call_queue'

# Fixed code
def broadcast_apply(cls, axis, apply_func, left, right, other_name="r"):
    """Broadcast the right partitions to left and apply a function.

    Note: This will often be overridden by implementations. It materializes the
        entire partitions of the right and applies them to the left through `apply`.

    Parameters
    ----------
        axis: The axis to apply and broadcast over.
        apply_func: The function to apply.
        left: The left partitions.
        right: The right partitions.
        other_name: Name of key-value argument for `apply_func` that
            obtains `right`. (optional, by default it's `"r"`)

    Returns
    -------
        A new `np.array` of partition objects.
    """
    [obj.drain_call_queue() for row in right for obj in row]
    new_right = np.empty(shape=right.shape[axis], dtype=object)

    if axis:
        right = right.T

    for i in range(len(right)):
        new_right[i] = pandas.concat(
            [right[i][j].get() for j in range(len(right[i]))], axis=axis ^ 1
        )
    right = new_right.T if axis else new_right

    new_partitions = np.array(
        [
            [
                part.add_to_apply_calls(
                    apply_func,
                    **{other_name: right[col_idx] if axis else right[row_idx]},
                )
                for col_idx, part in enumerate(left[row_idx])
            ]
            for row_idx in range(len(left))
        ]
    )

    return new_partitions