Repository: pandas-dev/pandas

Issue:
DataFrame.plot() produces incorrect legend labels when plotting multiple series on the same axis
#### Code Sample, a copy-pastable example if possible
```python
import pandas as pd
import matplotlib.pyplot as plt
df = pd.DataFrame(data=[[1, 1, 1, 1], [2, 2, 4, 8]], columns=['x', 'r', 'g', 'b'])
fig, ax = plt.subplots(nrows=1, ncols=3)
# Left plot
df.plot(x='x', y='r', linewidth=0, marker='o', color='r', ax=ax[0])
df.plot(x='x', y='g', linewidth=1, marker='x', color='g', ax=ax[0])
df.plot(x='x', y='b', linewidth=1, marker='o', color='b', ax=ax[0])
# Center plot
df.plot(x='x', y='b', linewidth=1, marker='o', color='b', ax=ax[1])
df.plot(x='x', y='r', linewidth=0, marker='o', color='r', ax=ax[1])
df.plot(x='x', y='g', linewidth=1, marker='x', color='g', ax=ax[1])
# Right plot
df.plot(x='x', y='g', linewidth=1, marker='x', color='g', ax=ax[2])
df.plot(x='x', y='b', linewidth=1, marker='o', color='b', ax=ax[2])
df.plot(x='x', y='r', linewidth=0, marker='o', color='r', ax=ax[2])
# Produces correct output when uncommented:
# ax[0].legend()
# ax[1].legend()
# ax[2].legend()
plt.show()
```

#### Problem description
**Note:** Possibly related to #14958, #17939, #14563, however this issue discusses how the behaviour depends on the order in which `df.plot()` is called

- In certain situations, `df.plot()` may generate incorrect legend labels (see example)
- Incorrect legend labels may appear when `df.plot()` plots multiple series on the same axis
- Plotting only one series per axis always appears to produce the correct legend label
- When multiple series are plotted on the same axis:
    - Only the last `df.plot()` call will produce the correct legend label
    - Calling `ax.legend()` after the final `df.plot()` will produce the correct legend entry
- Calling `df.plot()` with `markerstyle=''` always appears produces the correct legend label for that series regardless of the order in which `df.plot()` is called, or the number of other series on the same axis

#### Expected Output
**This is the incorrect output produced by the sample code**
Note that in each plot, only the last series to be plotted has a correct legend label
![image](https://user-images.githubusercontent.com/2927085/32679354-07811222-c61b-11e7-9e3d-603e5a539875.png)
**This is the expected output**
![image](https://user-images.githubusercontent.com/2927085/32679601-cf13659c-c61b-11e7-9e45-d15e8120b8a7.png)


#### Output of ``pd.show_versions()``

<details>

[paste the output of ``pd.show_versions()`` here below this line]
INSTALLED VERSIONS
------------------
commit: None
python: 3.5.4.final.0
python-bits: 64
OS: Linux
OS-release: 4.10.0-37-generic
machine: x86_64
processor: x86_64
byteorder: little
LC_ALL: None
LANG: en_US.UTF-8
LOCALE: en_US.UTF-8

pandas: 0.21.0
pytest: None
pip: 9.0.1
setuptools: 36.5.0.post20170921
Cython: None
numpy: 1.13.3
scipy: 0.19.1
pyarrow: None
xarray: None
IPython: 6.1.0
sphinx: None
patsy: None
dateutil: 2.6.1
pytz: 2017.3
blosc: None
bottleneck: None
tables: None
numexpr: None
feather: None
matplotlib: 2.1.0
openpyxl: None
xlrd: None
xlwt: None
xlsxwriter: None
lxml: None
bs4: None
html5lib: None
sqlalchemy: None
pymysql: None
psycopg2: None
jinja2: None
s3fs: None
fastparquet: None
pandas_gbq: None
pandas_datareader: None

</details>



Discussion:
cc @TomAugspurger 

@scottlawsonbc : Could you provide snapshots of what you're seeing currently?
@gfyoung there are two screenshots under the expected output section
> there are two screenshots under the expected output section

Yes, I see that, but which, if any, represent what you are seeing currently?  Or are both snapshots representative of what you *would expect* to see?
@gfyoung I understand your question now. I see how the plots are confusing and I should have explained their meaning in a way that is more clear.

At the bottom of my example code, I commented out several lines:

```
# Produces correct output when uncommented:
# ax[0].legend()
# ax[1].legend()
# ax[2].legend()
```

When these lines are commented out, the sample code produces a plot with **incorrect legend labels.** If you run the sample code exactly as I provided in the original issue, then it will produce the erroneous incorrect output.

When these lines are uncommented, the sample code produces a plot with the **correct legend labels.** However, the issue is that pandas should be producing this output without requiring these lines to be run.

In the expected output section of my issue, the first plot shows the output which is incorrect, and the second plot shows what the correct output should be (intended as a reference).

Does this answer your question? I will update my issue to make this more clear.
Ah, okay.  That makes sense.  If you could update the issue to reflect your answer, that would be great.
@scottlawsonbc do you have time to debug where things are going wrong? https://github.com/pandas-dev/pandas/blob/7f4c960c7d0c100e5df9f0a3bc68401fc4eace4d/pandas/plotting/_core.py#L495 https://github.com/pandas-dev/pandas/blob/7f4c960c7d0c100e5df9f0a3bc68401fc4eace4d/pandas/plotting/_core.py#L384 may be good starting points (or maybe not, haven't touched that code in a while)

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
@@ -562,7 +562,7 @@ def _add_legend_handle(self, handle, label, index=None):
             self.legend_labels.append(label)
 
     def _make_legend(self):
-        ax, leg = self._get_ax_legend(self.axes[0])
+        ax, leg, handle = self._get_ax_legend_handle(self.axes[0])
 
         handles = []
         labels = []
@@ -571,7 +571,8 @@ def _make_legend(self):
         if not self.subplots:
             if leg is not None:
                 title = leg.get_title().get_text()
-                handles = leg.legendHandles
+                # Replace leg.LegendHandles because it misses marker info
+                handles.extend(handle)
                 labels = [x.get_text() for x in leg.get_texts()]
 
             if self.legend:
@@ -581,6 +582,7 @@ def _make_legend(self):
 
                 handles += self.legend_handles
                 labels += self.legend_labels
+
                 if self.legend_title is not None:
                     title = self.legend_title
 
@@ -592,8 +594,14 @@ def _make_legend(self):
                 if ax.get_visible():
                     ax.legend(loc="best")
 
-    def _get_ax_legend(self, ax):
+    def _get_ax_legend_handle(self, ax):
+        """
+        Take in axes and return ax, legend and handle under different scenarios
+        """
         leg = ax.get_legend()
+
+        # Get handle from axes
+        handle, _ = ax.get_legend_handles_labels()
         other_ax = getattr(ax, "left_ax", None) or getattr(ax, "right_ax", None)
         other_leg = None
         if other_ax is not None:
@@ -601,7 +609,7 @@ def _get_ax_legend(self, ax):
         if leg is None and other_leg is not None:
             leg = other_leg
             ax = other_ax
-        return ax, leg
+        return ax, leg, handle
 
     @cache_readonly
     def plt(self):


Test patch:
diff --git a/pandas/tests/plotting/common.py b/pandas/tests/plotting/common.py
--- a/pandas/tests/plotting/common.py
+++ b/pandas/tests/plotting/common.py
@@ -103,6 +103,28 @@ def _check_legend_labels(self, axes, labels=None, visible=True):
             else:
                 assert ax.get_legend() is None
 
+    def _check_legend_marker(self, ax, expected_markers=None, visible=True):
+        """
+        Check ax has expected legend markers
+
+        Parameters
+        ----------
+        ax : matplotlib Axes object
+        expected_markers : list-like
+            expected legend markers
+        visible : bool
+            expected legend visibility. labels are checked only when visible is
+            True
+        """
+        if visible and (expected_markers is None):
+            raise ValueError("Markers must be specified when visible is True")
+        if visible:
+            handles, _ = ax.get_legend_handles_labels()
+            markers = [handle.get_marker() for handle in handles]
+            assert markers == expected_markers
+        else:
+            assert ax.get_legend() is None
+
     def _check_data(self, xp, rs):
         """
         Check each axes has identical lines
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
@@ -1881,6 +1881,31 @@ def test_df_legend_labels(self):
         self._check_legend_labels(ax, labels=["LABEL_b", "LABEL_c"])
         assert df5.columns.tolist() == ["b", "c"]
 
+    def test_missing_marker_multi_plots_on_same_ax(self):
+        # GH 18222
+        df = pd.DataFrame(
+            data=[[1, 1, 1, 1], [2, 2, 4, 8]], columns=["x", "r", "g", "b"]
+        )
+        fig, ax = self.plt.subplots(nrows=1, ncols=3)
+        # Left plot
+        df.plot(x="x", y="r", linewidth=0, marker="o", color="r", ax=ax[0])
+        df.plot(x="x", y="g", linewidth=1, marker="x", color="g", ax=ax[0])
+        df.plot(x="x", y="b", linewidth=1, marker="o", color="b", ax=ax[0])
+        self._check_legend_labels(ax[0], labels=["r", "g", "b"])
+        self._check_legend_marker(ax[0], expected_markers=["o", "x", "o"])
+        # Center plot
+        df.plot(x="x", y="b", linewidth=1, marker="o", color="b", ax=ax[1])
+        df.plot(x="x", y="r", linewidth=0, marker="o", color="r", ax=ax[1])
+        df.plot(x="x", y="g", linewidth=1, marker="x", color="g", ax=ax[1])
+        self._check_legend_labels(ax[1], labels=["b", "r", "g"])
+        self._check_legend_marker(ax[1], expected_markers=["o", "o", "x"])
+        # Right plot
+        df.plot(x="x", y="g", linewidth=1, marker="x", color="g", ax=ax[2])
+        df.plot(x="x", y="b", linewidth=1, marker="o", color="b", ax=ax[2])
+        df.plot(x="x", y="r", linewidth=0, marker="o", color="r", ax=ax[2])
+        self._check_legend_labels(ax[2], labels=["g", "b", "r"])
+        self._check_legend_marker(ax[2], expected_markers=["x", "o", "o"])
+
     def test_legend_name(self):
         multi = DataFrame(
             randn(4, 4),