# Buggy code
def _create_arithmetic_method(cls, op):
    # Note: this handles both arithmetic and comparison methods.
    def method(self, other):
        is_arithmetic = True if op.__name__ in ops.ARITHMETIC_BINOPS else False

        is_other_array = False
        if not is_scalar(other):
            is_other_array = True
            other = np.asarray(other)

        self_is_na = self.isna()
        other_is_na = pd.isna(other)
        mask = self_is_na | other_is_na

        chunks = []
        mask_chunks = []
        start = 0
        for chunk_array in self._arrow_array.chunks:
            chunk_array = np.asarray(chunk_array.to_pandas())
            end = start + len(chunk_array)
            chunk_mask = mask[start:end]
            chunk_valid = ~chunk_mask

            if is_arithmetic:
                result = np.empty(chunk_array.shape, dtype=object)
            else:
                result = np.zeros(chunk_array.shape, dtype=bool)

            chunk_other = other
            if is_other_array:
                chunk_other = other[start:end]
                chunk_other = chunk_other[chunk_valid]

            # calculate only for both not None
            result[chunk_valid] = op(chunk_array[chunk_valid], chunk_other)

            if is_arithmetic:
                chunks.append(pa.array(result, type=pa.string(), from_pandas=True))
            else:
                chunks.append(result)
                mask_chunks.append(chunk_mask)

        if is_arithmetic:
            return ArrowStringArray(pa.chunked_array(chunks))
        else:
            return pd.arrays.BooleanArray(
                np.concatenate(chunks), np.concatenate(mask_chunks)
            )

    return set_function_name(method, f"__{op.__name__}__", cls)


# Traceback (TypeError)
In [1]: import mars.dataframe as md

In [2]: df = md.DataFrame({'a': [1, 2, 3], 'b': ['a', 'b', 'c']})

In [3]: df['b'] = df['b'].astype(md.ArrowStringDtype())

In [6]: df.groupby('b').count()
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
~/miniconda3/lib/python3.7/site-packages/pandas/core/construction.py in _try_cast(arr, dtype, copy, raise_cast_failure)
545         elif not is_extension_array_dtype(subarr):
--> 546             subarr = construct_1d_ndarray_preserving_na(subarr, dtype, copy=copy)
547     except OutOfBoundsDatetime:

~/miniconda3/lib/python3.7/site-packages/pandas/core/dtypes/cast.py in construct_1d_ndarray_preserving_na(values, dtype, copy)
1506     """
-> 1507     subarr = np.array(values, dtype=dtype, copy=copy)
1508

TypeError: Cannot interpret '<mars.dataframe.arrays.ArrowStringDtype object at 0x7f7f81874150>' as a data type

During handling of the above exception, another exception occurred:

TypeError                                 Traceback (most recent call last)
<ipython-input-6-884154032b17> in <module>
----> 1 df.groupby('b').count()

~/Workspace/mars/mars/dataframe/groupby/__init__.py in <lambda>(groupby, **kw)
40         setattr(cls, 'max', lambda groupby, **kw: agg(groupby, 'max', **kw))
41         setattr(cls, 'min', lambda groupby, **kw: agg(groupby, 'min', **kw))
---> 42         setattr(cls, 'count', lambda groupby, **kw: agg(groupby, 'count', **kw))
43         setattr(cls, 'size', lambda groupby, **kw: agg(groupby, 'size', **kw))
44         setattr(cls, 'mean', lambda groupby, **kw: agg(groupby, 'mean', **kw))

~/Workspace/mars/mars/dataframe/groupby/aggregation.py in agg(groupby, func, method, *args, **kwargs)
650     agg_op = DataFrameGroupByAgg(func=func, method=method, raw_func=func,
651                                  groupby_params=groupby.op.groupby_params)
--> 652     return agg_op(groupby)

~/Workspace/mars/mars/dataframe/groupby/aggregation.py in __call__(self, groupby)
201
202         if self.output_types[0] == OutputType.dataframe:
--> 203             return self._call_dataframe(groupby, df)
204         else:
205             return self._call_series(groupby, df)

~/Workspace/mars/mars/dataframe/groupby/aggregation.py in _call_dataframe(self, groupby, input_df)
144
145     def _call_dataframe(self, groupby, input_df):
--> 146         grouped = groupby.op.build_mock_groupby()
147         agg_df = grouped.aggregate(self.func)
148

~/Workspace/mars/mars/dataframe/groupby/core.py in build_mock_groupby(self, **kwargs)
98         in_df = self.inputs[0]
99         if self.is_dataframe_obj:
--> 100             empty_df = build_empty_df(in_df.dtypes, index=pd.RangeIndex(2))
101             obj_dtypes = in_df.dtypes[in_df.dtypes == np.dtype('O')]
102             empty_df[obj_dtypes.index] = 'O'

~/Workspace/mars/mars/dataframe/utils.py in build_empty_df(dtypes, index)
446     df = pd.DataFrame(columns=pd.RangeIndex(len(columns)), index=index)
447     for i, d in enumerate(dtypes):
--> 448         df[i] = pd.Series(dtype=d, index=index)
449     df.columns = columns
450     return df

~/miniconda3/lib/python3.7/site-packages/pandas/core/series.py in __init__(self, data, index, dtype, name, copy, fastpath)
254                 data = data._data
255             elif is_dict_like(data):
--> 256                 data, index = self._init_dict(data, index, dtype)
257                 dtype = None
258                 copy = False

~/miniconda3/lib/python3.7/site-packages/pandas/core/series.py in _init_dict(self, data, index, dtype)
347         # TODO: passing np.float64 to not break anything yet. See GH-17261
348         s = create_series_with_explicit_dtype(
--> 349             values, index=keys, dtype=dtype, dtype_if_empty=np.float64
350         )
351

~/miniconda3/lib/python3.7/site-packages/pandas/core/construction.py in create_series_with_explicit_dtype(data, index, dtype, name, copy, fastpath, dtype_if_empty)
623         dtype = dtype_if_empty
624     return Series(
--> 625         data=data, index=index, dtype=dtype, name=name, copy=copy, fastpath=fastpath
626     )

~/miniconda3/lib/python3.7/site-packages/pandas/core/series.py in __init__(self, data, index, dtype, name, copy, fastpath)
303                     data = data.copy()
304             else:
--> 305                 data = sanitize_array(data, index, dtype, copy, raise_cast_failure=True)
306
307                 data = SingleBlockManager(data, index, fastpath=True)

~/miniconda3/lib/python3.7/site-packages/pandas/core/construction.py in sanitize_array(data, index, dtype, copy, raise_cast_failure)
447         subarr = _try_cast(arr, dtype, copy, raise_cast_failure)
448     else:
--> 449         subarr = _try_cast(data, dtype, copy, raise_cast_failure)
450
451     # scalar like, GH

~/miniconda3/lib/python3.7/site-packages/pandas/core/construction.py in _try_cast(arr, dtype, copy, raise_cast_failure)
560             dtype = cast(ExtensionDtype, dtype)
561             array_type = dtype.construct_array_type()._from_sequence
--> 562             subarr = array_type(arr, dtype=dtype, copy=copy)
563         elif dtype is not None and raise_cast_failure:
564             raise

~/Workspace/mars/mars/dataframe/arrays.py in _from_sequence(cls, scalars, dtype, copy)
253     def _from_sequence(cls, scalars, dtype=None, copy=False):
254         if not hasattr(scalars, 'dtype'):
--> 255             ret = np.empty(len(scalars), dtype=object)
256             for i, s in enumerate(scalars):
257                 ret[i] = s

TypeError: object of type 'pyarrow.lib.NullScalar' has no len()

# Fixed code
def _create_arithmetic_method(cls, op):
    # Note: this handles both arithmetic and comparison methods.
    def method(self, other):
        is_arithmetic = True if op.__name__ in ops.ARITHMETIC_BINOPS else False
        pandas_only = cls._pandas_only()

        is_other_array = False
        if not is_scalar(other):
            is_other_array = True
            other = np.asarray(other)

        self_is_na = self.isna()
        other_is_na = pd.isna(other)
        mask = self_is_na | other_is_na

        if pa is None or pandas_only:
            if is_arithmetic:
                ret = np.empty(self.shape, dtype=object)
            else:
                ret = np.zeros(self.shape, dtype=bool)
            valid = ~mask
            arr = (
                self._arrow_array.to_pandas().to_numpy()
                if self._use_arrow
                else self._ndarray
            )
            o = other[valid] if is_other_array else other
            ret[valid] = op(arr[valid], o)
            if is_arithmetic:
                return ArrowStringArray(ret)
            else:
                return pd.arrays.BooleanArray(ret, mask)

        chunks = []
        mask_chunks = []
        start = 0
        for chunk_array in self._arrow_array.chunks:
            chunk_array = np.asarray(chunk_array.to_pandas())
            end = start + len(chunk_array)
            chunk_mask = mask[start:end]
            chunk_valid = ~chunk_mask

            if is_arithmetic:
                result = np.empty(chunk_array.shape, dtype=object)
            else:
                result = np.zeros(chunk_array.shape, dtype=bool)

            chunk_other = other
            if is_other_array:
                chunk_other = other[start:end]
                chunk_other = chunk_other[chunk_valid]

            # calculate only for both not None
            result[chunk_valid] = op(chunk_array[chunk_valid], chunk_other)

            if is_arithmetic:
                chunks.append(pa.array(result, type=pa.string(), from_pandas=True))
            else:
                chunks.append(result)
                mask_chunks.append(chunk_mask)

        if is_arithmetic:
            return ArrowStringArray(pa.chunked_array(chunks))
        else:
            return pd.arrays.BooleanArray(
                np.concatenate(chunks), np.concatenate(mask_chunks)
            )

    return set_function_name(method, f"__{op.__name__}__", cls)