Repository: mars-project/mars

Issue:
[BUG] Failed to set a scalar which is a tensor to DataFrame
<!--
Thank you for your contribution!

Please review https://github.com/mars-project/mars/blob/master/CONTRIBUTING.rst before opening an issue.
-->

**Describe the bug**

For now, we cannot set a scalar which is a tensor to DataFrame.

**To Reproduce**
To help us reproducing this bug, please provide information below:
1. Your Python version
2. The version of Mars you use
3. Versions of crucial packages, such as numpy, scipy and protobuf
4. Full stack of the error.
5. Minimized code to reproduce the error.

```
In [1]: import mars.dataframe as md                                                                                                                                                                         

In [2]: import mars.tensor as mt                                                                                                                                                                            

In [3]: df = md.DataFrame(mt.random.rand(10 ,3))                                                                                                                                                            

In [4]: df['a'] = df[0].mean()                                                                                                                                                                              
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-4-fdfc58da199a> in <module>
----> 1 df['a'] = df[0].mean()

~/Workspace/mars/mars/dataframe/indexing/setitem.py in dataframe_setitem(df, col, value)
    172 def dataframe_setitem(df, col, value):
    173     op = DataFrameSetitem(target=df, indexes=col, value=value)
--> 174     return op(df, value)

~/Workspace/mars/mars/utils.py in _inner(*args, **kwargs)
    449         def _inner(*args, **kwargs):
    450             with self:
--> 451                 return func(*args, **kwargs)
    452 
    453         return _inner

~/Workspace/mars/mars/dataframe/indexing/setitem.py in __call__(self, target, value)
     69                 value_dtype = value.dtype
     70             elif is_list_like(value) or isinstance(value, TENSOR_TYPE):
---> 71                 value = asseries(value, index=target.index)
     72                 inputs.append(value)
     73                 value_dtype = value.dtype

~/Workspace/mars/mars/dataframe/initializer.py in __init__(self, data, index, dtype, name, copy, chunk_size, gpu, sparse)
     66             if chunk_size is not None:
     67                 data = data.rechunk(chunk_size)
---> 68             series = series_from_tensor(data, index=index, name=name, gpu=gpu, sparse=sparse)
     69         elif isinstance(index, INDEX_TYPE):
     70             series = series_from_tensor(astensor(data, chunk_size=chunk_size), index=index,

~/Workspace/mars/mars/dataframe/datasource/from_tensor.py in series_from_tensor(tensor, index, name, gpu, sparse)
    467 def series_from_tensor(tensor, index=None, name=None, gpu=None, sparse=False):
    468     if tensor.ndim > 1 or tensor.ndim <= 0:
--> 469         raise TypeError(f'Not support create DataFrame from {tensor.ndim} dims tensor')
    470     gpu = tensor.op.gpu if gpu is None else gpu
    471     op = SeriesFromTensor(dtype=tensor.dtype, gpu=gpu, sparse=sparse)

TypeError: Not support create DataFrame from 0 dims tensor
```



Discussion:

Resolving patch:
diff --git a/mars/dataframe/datasource/from_tensor.py b/mars/dataframe/datasource/from_tensor.py
--- a/mars/dataframe/datasource/from_tensor.py
+++ b/mars/dataframe/datasource/from_tensor.py
@@ -470,7 +470,7 @@ def __call__(self, input_tensor, index, name):
 
 def series_from_tensor(tensor, index=None, name=None, gpu=None, sparse=False):
     if tensor.ndim > 1 or tensor.ndim <= 0:
-        raise TypeError(f'Not support create DataFrame from {tensor.ndim} dims tensor')
+        raise TypeError(f'Not support create Series from {tensor.ndim} dims tensor')
     gpu = tensor.op.gpu if gpu is None else gpu
     op = SeriesFromTensor(dtype=tensor.dtype, gpu=gpu, sparse=sparse)
     return op(tensor, index, name)
diff --git a/mars/dataframe/indexing/setitem.py b/mars/dataframe/indexing/setitem.py
--- a/mars/dataframe/indexing/setitem.py
+++ b/mars/dataframe/indexing/setitem.py
@@ -58,10 +58,17 @@ def _set_inputs(self, inputs):
         if len(inputs) > 1:
             self._value = self._inputs[-1]
 
+    @staticmethod
+    def _is_scalar_tensor(t):
+        return isinstance(t, TENSOR_TYPE) and t.ndim == 0
+
     def __call__(self, target: DataFrame, value):
         inputs = [target]
         if np.isscalar(value):
             value_dtype = np.array(value).dtype
+        elif self._is_scalar_tensor(value):
+            inputs.append(value)
+            value_dtype = value.dtype
         else:
             if isinstance(value, (pd.Series, SERIES_TYPE)):
                 value = asseries(value)
@@ -94,8 +101,9 @@ def tile(cls, op):
         value = op.value
         col = op.indexes
         columns = target.columns_value.to_pandas()
+        is_value_scalar = np.isscalar(value) or cls._is_scalar_tensor(value)
 
-        if not np.isscalar(value):
+        if not is_value_scalar:
             # check if all chunk's index_value are identical
             target_chunk_index_values = [c.index_value for c in target.chunks
                                          if c.index[1] == 0]
@@ -122,8 +130,10 @@ def tile(cls, op):
                     out_chunks.append(c)
                 else:
                     chunk_op = op.copy().reset_key()
-                    if np.isscalar(value):
+                    if pd.api.types.is_scalar(value):
                         chunk_inputs = [c]
+                    elif is_value_scalar:
+                        chunk_inputs = [c, value.chunks[0]]
                     else:
                         value_chunk = value.cix[c.index[0], ]
                         chunk_inputs = [c, value_chunk]


Test patch:
diff --git a/mars/dataframe/indexing/tests/test_indexing_execution.py b/mars/dataframe/indexing/tests/test_indexing_execution.py
--- a/mars/dataframe/indexing/tests/test_indexing_execution.py
+++ b/mars/dataframe/indexing/tests/test_indexing_execution.py
@@ -612,12 +612,14 @@ def testSetitem(self):
         df['c3'] = df['c3'] + 1
         df['c10'] = 10
         df[4] = mt.tensor(data2, chunk_size=4)
+        df['d1'] = df['c4'].mean()
 
         result = self.executor.execute_dataframe(df, concat=True)[0]
         expected = data.copy()
         expected['c3'] = expected['c3'] + 1
         expected['c10'] = 10
         expected[4] = data2
+        expected['d1'] = data['c4'].mean()
         pd.testing.assert_frame_equal(result, expected)
 
     def testResetIndexExecution(self):