Repository: pandas-dev/pandas

Issue:
ERR: setting a column with a scalar and no index should raise
xref https://github.com/pandas-dev/pandas/issues/10017#issuecomment-312845661

This should raise like [5] below. 
```
In [2]: df = DataFrame()

In [3]: df['foo'] = 1

In [4]: df
Out[4]: 
Empty DataFrame
Columns: [foo]
Index: []
```

```
In [5]: DataFrame({'foo': 1})
ValueError: If using all scalar values, you must pass an index
I
n [17]: df.loc[1] = 1
\ValueError: cannot set a frame with no defined columns
```

Even though this is technically valid code (in [4]), IOW the scalar is reindexed to a 0-len index, this immediately loses information This should raise an error.


Discussion:
cc @AwasthiMaddy
I'm not convinced this is a bug... as @jreback says, ``[3]`` is valid code, and the boundary with "losing information" is not so obvious. What about

``` python
In [2]: s = pd.Series(range(4))

In [3]: s.loc[5:] = 1
```
? I'm pretty sure we want to allow it. On the other hand, in ``[5]`` you are initializing, so it makes sense to require more explicit "instructions" (as in ``pd.DataFrame({'foo' : 1}, index=[])``, which works fine).

I see instead the incoherence with ``[17]``, but I think it can be justified (because a ``DataFrame`` has a better defined meaning than one with no columns; and because setting an entire column to a scalar is a more usual operation than setting an entire row to a scalar) - and anyway, I would ideally prefer to fix ``[17]`` (which should expand the index) than to forbid ``[3]``.

Resolving patch:
diff --git a/pandas/core/frame.py b/pandas/core/frame.py
--- a/pandas/core/frame.py
+++ b/pandas/core/frame.py
@@ -2531,13 +2531,17 @@ def _ensure_valid_index(self, value):
         passed value
         """
         # GH5632, make sure that we are a Series convertible
-        if not len(self.index) and is_list_like(value):
+        if not len(self.index):
+            if not is_list_like(value):
+                # GH16823, Raise an error due to loss of information
+                raise ValueError('If using all scalar values, you must pass'
+                                 ' an index')
             try:
                 value = Series(value)
             except:
-                raise ValueError('Cannot set a frame with no defined index '
-                                 'and a value that cannot be converted to a '
-                                 'Series')
+                raise ValueError('Cannot set a frame with no defined'
+                                 'index and a value that cannot be '
+                                 'converted to a Series')
 
             self._data = self._data.reindex_axis(value.index.copy(), axis=1,
                                                  fill_value=np.nan)
diff --git a/pandas/core/reshape/pivot.py b/pandas/core/reshape/pivot.py
--- a/pandas/core/reshape/pivot.py
+++ b/pandas/core/reshape/pivot.py
@@ -454,6 +454,9 @@ def crosstab(index, columns, values=None, rownames=None, colnames=None,
 
     from pandas import DataFrame
     df = DataFrame(data, index=common_idx)
+    if not len(df):
+        return DataFrame(index=common_idx)
+
     if values is None:
         df['__dummy__'] = 0
         kwargs = {'aggfunc': len, 'fill_value': 0}


Test patch:
diff --git a/pandas/tests/frame/test_indexing.py b/pandas/tests/frame/test_indexing.py
--- a/pandas/tests/frame/test_indexing.py
+++ b/pandas/tests/frame/test_indexing.py
@@ -721,6 +721,11 @@ def test_setitem_empty_frame_with_boolean(self):
                 df[df > df2] = 47
                 assert_frame_equal(df, df2)
 
+    def test_setitem_scalars_no_index(self):
+        # GH16823
+        df = DataFrame()
+        pytest.raises(ValueError, df.__setitem__, 'foo', 1)
+
     def test_getitem_empty_frame_with_boolean(self):
         # Test for issue #11859
 
diff --git a/pandas/tests/indexing/test_loc.py b/pandas/tests/indexing/test_loc.py
--- a/pandas/tests/indexing/test_loc.py
+++ b/pandas/tests/indexing/test_loc.py
@@ -423,15 +423,14 @@ def test_loc_setitem_consistency(self):
 
     def test_loc_setitem_consistency_empty(self):
         # empty (essentially noops)
-        expected = DataFrame(columns=['x', 'y'])
-        expected['x'] = expected['x'].astype(np.int64)
+        # GH16823
         df = DataFrame(columns=['x', 'y'])
-        df.loc[:, 'x'] = 1
-        tm.assert_frame_equal(df, expected)
+        with tm.assert_raises_regex(ValueError, 'If using all scalar values'):
+            df.loc[:, 'x'] = 1
 
         df = DataFrame(columns=['x', 'y'])
-        df['x'] = 1
-        tm.assert_frame_equal(df, expected)
+        with tm.assert_raises_regex(ValueError, 'If using all scalar values'):
+            df['x'] = 1
 
     def test_loc_setitem_consistency_slice_column_len(self):
         # .loc[:,column] setting with slice == len of the column
diff --git a/pandas/tests/indexing/test_partial.py b/pandas/tests/indexing/test_partial.py
--- a/pandas/tests/indexing/test_partial.py
+++ b/pandas/tests/indexing/test_partial.py
@@ -575,24 +575,16 @@ def f():
     def test_partial_set_empty_frame_row(self):
         # GH5720, GH5744
         # don't create rows when empty
-        expected = DataFrame(columns=['A', 'B', 'New'],
-                             index=pd.Index([], dtype='int64'))
-        expected['A'] = expected['A'].astype('int64')
-        expected['B'] = expected['B'].astype('float64')
-        expected['New'] = expected['New'].astype('float64')
-
         df = DataFrame({"A": [1, 2, 3], "B": [1.2, 4.2, 5.2]})
         y = df[df.A > 5]
-        y['New'] = np.nan
-        tm.assert_frame_equal(y, expected)
-        # tm.assert_frame_equal(y,expected)
+        # GH16823
+        # Setting a column with a scalar and no index should raise
+        with tm.assert_raises_regex(ValueError, 'If using all scalar values'):
+            y['New'] = np.nan
 
-        expected = DataFrame(columns=['a', 'b', 'c c', 'd'])
-        expected['d'] = expected['d'].astype('int64')
         df = DataFrame(columns=['a', 'b', 'c c'])
-        df['d'] = 3
-        tm.assert_frame_equal(df, expected)
-        tm.assert_series_equal(df['c c'], Series(name='c c', dtype=object))
+        with tm.assert_raises_regex(ValueError, 'If using all scalar values'):
+            df['d'] = 3
 
         # reindex columns is ok
         df = DataFrame({"A": [1, 2, 3], "B": [1.2, 4.2, 5.2]})
diff --git a/pandas/tests/reshape/test_pivot.py b/pandas/tests/reshape/test_pivot.py
--- a/pandas/tests/reshape/test_pivot.py
+++ b/pandas/tests/reshape/test_pivot.py
@@ -1226,7 +1226,8 @@ def test_crosstab_no_overlap(self):
         s2 = pd.Series([4, 5, 6], index=[4, 5, 6])
 
         actual = crosstab(s1, s2)
-        expected = pd.DataFrame()
+        expected = pd.DataFrame(
+            index=pd.Index([], dtype='int64')).astype('int64')
 
         tm.assert_frame_equal(actual, expected)