Repository: pandas-dev/pandas

Issue:
Cannot plot empty Series
#### Code Sample

```python
import pandas as pd
import matplotlib.pyplot as plt

ax = plt.subplots(1)[1]
ax.set_xlim((0, 10))
ax.set_ylim((0, 10))
x = pd.Series([], index=pd.Index([], dtype=float), dtype=float)
x.plot(ax=ax)
```

#### Problem description

It is not possible to `.plot()` an empty `Series` or `DataFrame`. When `.plot()` is called on an empty `Series` or `DataFrame`, an exception is raised:

<pre>
~/software/venv-scipy/lib/python3.7/site-packages/pandas/plotting/_matplotlib/core.py in _compute_plot_data(self)
    412         # no non-numeric frames or series allowed
    413         if is_empty:
--> 414             raise TypeError("no numeric data to plot")
    415 
    416         # GH25587: cast ExtensionArray of pandas (IntegerArray, etc.) to

TypeError: no numeric data to plot
</pre>

This is problematic because code path which are supposed to work can suddenly fail when the data to plot is empty (for example, there was no data for the requested time-range).

In order to be robust to this, the code needs to include a lot of things such as:

```python
if not x.empty:
    x.plot(ax=ax)
``

#### Expected Output

#### Output of ``pd.show_versions()``

<details>
INSTALLED VERSIONS
------------------
commit           : None
python           : 3.7.4.final.0
python-bits      : 64
OS               : Linux
OS-release       : 4.19.0-5-amd64
machine          : x86_64
processor        : 
byteorder        : little
LC_ALL           : None
LANG             : fr_FR.utf8
LOCALE           : fr_FR.UTF-8

pandas           : 0.25.0
numpy            : 1.17.0
pytz             : 2019.2
dateutil         : 2.8.0
pip              : 18.1
setuptools       : 40.8.0
Cython           : None
pytest           : None
hypothesis       : None
sphinx           : None
blosc            : None
feather          : None
xlsxwriter       : None
lxml.etree       : None
html5lib         : None
pymysql          : None
psycopg2         : None
jinja2           : 2.10.1
IPython          : 7.7.0
pandas_datareader: None
bs4              : None
bottleneck       : None
fastparquet      : None
gcsfs            : None
lxml.etree       : None
matplotlib       : 3.1.1
numexpr          : None
odfpy            : None
openpyxl         : None
pandas_gbq       : None
pyarrow          : None
pytables         : None
s3fs             : None
scipy            : None
sqlalchemy       : None
tables           : None
xarray           : None
xlrd             : None
xlwt             : None
xlsxwriter       : None
</details>



Discussion:
Can I try to fix this?
I'm not sure we've decided on the correct behavior here yet. Pandas does quite a bit more preprocessing on the data before plotting. We'll need to be careful about the ways a user can get up here.

If they explicitly pass an empty series or a DataFrame with no rows, that's fine.

We can also end up at this point if there are no columns after a `select_dtypes`. In that case, I think we still want to raise.
just for my curiosity, what kind of output do you expect for empty series/frame? something like below?

![Screen Shot 2019-08-22 at 9 18 14 AM](https://user-images.githubusercontent.com/9269816/63494570-43183100-c4be-11e9-84d5-a4f3a914d2cb.png)

I also feel like there are many preprocessings and selections on type as Tom mentioned above before plotting, probably adding a keyword might be a trade-off? @TomAugspurger something like `s.plot(ax=ax, allow_empty=True)`? and set default to `False` to preserve current pandas behaviour, so if `False` and either there is no column after `select_dtypes` or an empty series/frames is used for plotting, an error will raise. Otherwise, will silently plot an empty fig with a warning about no numeric data to plot.

does it make sense? i think the implementation won't be too complex for this @TomAugspurger 
I don't have a strong opinion here.

On Thu, Aug 22, 2019 at 2:27 AM Kaiqi Dong <notifications@github.com> wrote:

> just for my curiosity, what kind of output do you expect for empty
> series/frame? something like?
>
> [image: Screen Shot 2019-08-22 at 9 18 14 AM]
> <https://user-images.githubusercontent.com/9269816/63494570-43183100-c4be-11e9-84d5-a4f3a914d2cb.png>
>
> I also feel like now some pandas plotting do have selections on type as
> Tom mentioned above, probably adding a keyword might be a compromise?
> @TomAugspurger <https://github.com/TomAugspurger> something like
> s.plot(ax=ax, allow_empty=True)? and set default to False to preserve
> current pandas behaviour, so if False and either there is no column after
> select_dtypes or an empty series/frames is used for plotting, an error
> will raise. Otherwise, will silently plot an empty fig.
>
> does it make sense? i think the implementation won't be too complex for
> this @TomAugspurger <https://github.com/TomAugspurger>
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, view it on GitHub
> <https://github.com/pandas-dev/pandas/issues/27758?email_source=notifications&email_token=AAKAOIRTP7SCWTSDZCRR5TTQFY5XPA5CNFSM4IJLVZRKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOD44FDZA#issuecomment-523784676>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/AAKAOITAEZGBDGIOS6FI4HLQFY5XPANCNFSM4IJLVZRA>
> .
>

> just for my curiosity, what kind of output do you expect for empty series/frame? something like below?

@charlesdong1991, In the example given I'd assume the output should be the same as if the `.plot()` call was not done. There is really not much reason from a user point of view to fail in this case. Plotting empty data should just plot an empty line on the existing axis.

If the axis is created I guess I would expect it to have a type related the index dtype (the x axis should have dates if the Series/DataFrames as a `datetime64`-based index).

> set default to False to preserve current pandas behaviour, so if False and either there is no column after select_dtypes or an empty series/frames is used for plotting, an error will raise. 

I have the feeling that “there is no columns after `select_dtypes`” should/could be an error but that “there is no rows after `select_dtypes`” should not/would not need to be an error.

Would it make sense to use:

```python
numeric_data = data.select_dtypes(include=include_type, exclude=exclude_type)

try:
    # Instead of: is_empty = numeric_data.empty
    is_empty = numeric_data.columns.empty
except AttributeError:
      is_empty = not len(numeric_data)
````



Resolving patch:
diff --git a/pandas/plotting/_matplotlib/core.py b/pandas/plotting/_matplotlib/core.py
--- a/pandas/plotting/_matplotlib/core.py
+++ b/pandas/plotting/_matplotlib/core.py
@@ -418,7 +418,7 @@ def _compute_plot_data(self):
         numeric_data = data.select_dtypes(include=include_type, exclude=exclude_type)
 
         try:
-            is_empty = numeric_data.empty
+            is_empty = numeric_data.columns.empty
         except AttributeError:
             is_empty = not len(numeric_data)
 


Test patch:
diff --git a/pandas/tests/plotting/test_frame.py b/pandas/tests/plotting/test_frame.py
--- a/pandas/tests/plotting/test_frame.py
+++ b/pandas/tests/plotting/test_frame.py
@@ -3229,6 +3229,21 @@ def test_subplots_sharex_false(self):
         tm.assert_numpy_array_equal(axs[0].get_xticks(), expected_ax1)
         tm.assert_numpy_array_equal(axs[1].get_xticks(), expected_ax2)
 
+    def test_plot_no_rows(self):
+        # GH 27758
+        df = pd.DataFrame(columns=["foo"], dtype=int)
+        assert df.empty
+        ax = df.plot()
+        assert len(ax.get_lines()) == 1
+        line = ax.get_lines()[0]
+        assert len(line.get_xdata()) == 0
+        assert len(line.get_ydata()) == 0
+
+    def test_plot_no_numeric_data(self):
+        df = pd.DataFrame(["a", "b", "c"])
+        with pytest.raises(TypeError):
+            df.plot()
+
 
 def _generate_4_axes_via_gridspec():
     import matplotlib.pyplot as plt
diff --git a/pandas/tests/plotting/test_series.py b/pandas/tests/plotting/test_series.py
--- a/pandas/tests/plotting/test_series.py
+++ b/pandas/tests/plotting/test_series.py
@@ -909,3 +909,18 @@ def test_plot_xlim_for_series(self, kind):
 
         assert xlims[0] < 0
         assert xlims[1] > 1
+
+    def test_plot_no_rows(self):
+        # GH 27758
+        df = pd.Series(dtype=int)
+        assert df.empty
+        ax = df.plot()
+        assert len(ax.get_lines()) == 1
+        line = ax.get_lines()[0]
+        assert len(line.get_xdata()) == 0
+        assert len(line.get_ydata()) == 0
+
+    def test_plot_no_numeric_data(self):
+        df = pd.Series(["a", "b", "c"])
+        with pytest.raises(TypeError):
+            df.plot()