Repository: databricks/koalas

Issue:
Simplify the Plot Assessors
Current `plot` accessors have separate implementations for both `Series` and `DataFrame` despite having a very similar API structure.  Since there exist separate functions for both `plot_series` and `plot_frame`, a single class can handle plots for both.

I will open a PR for to solve this issue, ([cross ref](https://github.com/databricks/koalas/pull/1639#issuecomment-659824221))


Discussion:

Resolving patch:
diff --git a/databricks/koalas/frame.py b/databricks/koalas/frame.py
--- a/databricks/koalas/frame.py
+++ b/databricks/koalas/frame.py
@@ -84,7 +84,7 @@
 from databricks.koalas.missing.frame import _MissingPandasLikeDataFrame
 from databricks.koalas.ml import corr
 from databricks.koalas.typedef import infer_return_type, as_spark_type, DataFrameType, SeriesType
-from databricks.koalas.plot import KoalasFramePlotMethods
+from databricks.koalas.plot import KoalasPlotAccessor
 
 # These regular expression patterns are complied and defined here to avoid to compile the same
 # pattern every time it is used in _repr_ and _repr_html_ in DataFrame.
@@ -806,7 +806,7 @@ def add(self, other):
         return self + other
 
     # create accessor for plot
-    plot = CachedAccessor("plot", KoalasFramePlotMethods)
+    plot = CachedAccessor("plot", KoalasPlotAccessor)
 
     # create accessor for Spark related methods.
     spark = CachedAccessor("spark", SparkFrameMethods)
@@ -817,12 +817,12 @@ def add(self, other):
     def hist(self, bins=10, **kwds):
         return self.plot.hist(bins, **kwds)
 
-    hist.__doc__ = KoalasFramePlotMethods.hist.__doc__
+    hist.__doc__ = KoalasPlotAccessor.hist.__doc__
 
     def kde(self, bw_method=None, ind=None, **kwds):
         return self.plot.kde(bw_method, ind, **kwds)
 
-    kde.__doc__ = KoalasFramePlotMethods.kde.__doc__
+    kde.__doc__ = KoalasPlotAccessor.kde.__doc__
 
     add.__doc__ = _flex_doc_FRAME.format(
         desc="Addition", op_name="+", equiv="dataframe + other", reverse="radd"
diff --git a/databricks/koalas/plot.py b/databricks/koalas/plot.py
--- a/databricks/koalas/plot.py
+++ b/databricks/koalas/plot.py
@@ -1186,181 +1186,166 @@ def _get_plot_backend(backend=None):
     return module
 
 
-def _get_args_map(backend_name, data, kind, kwargs):
-    """Appropriate call args mapping for the backend
+class KoalasPlotAccessor(PandasObject):
     """
-    from databricks.koalas import DataFrame, Series
-
-    data_preprocessor_map = {
-        "pie": TopNPlot().get_top_n,
-        "bar": TopNPlot().get_top_n,
-        "barh": TopNPlot().get_top_n,
-        "scatter": TopNPlot().get_top_n,
-        "area": SampledPlot().get_sampled,
-        "line": SampledPlot().get_sampled,
-    }
-    # make the arguments values of matplotlib compatible with that of plotting backend
-    args_map = {
-        "plotly": {
-            "logx": "log_x",
-            "logy": "log_y",
-            "xlim": "range_x",
-            "ylim": "range_y",
-            "yerr": "error_y",
-            "xerr": "error_x",
-        }
-    }
-
-    if isinstance(data, Series):
-        positional_args = {
-            ("ax", None),
-            ("figsize", None),
-            ("use_index", True),
-            ("title", None),
-            ("grid", None),
-            ("legend", False),
-            ("style", None),
-            ("logx", False),
-            ("logy", False),
-            ("loglog", False),
-            ("xticks", None),
-            ("yticks", None),
-            ("xlim", None),
-            ("ylim", None),
-            ("rot", None),
-            ("fontsize", None),
-            ("colormap", None),
-            ("table", False),
-            ("yerr", None),
-            ("xerr", None),
-            ("label", None),
-            ("secondary_y", False),
-        }
-    elif isinstance(data, DataFrame):
-        positional_args = {
-            ("ax", None),
-            ("subplots", None),
-            ("sharex", None),
-            ("sharey", False),
-            ("layout", None),
-            ("figsize", None),
-            ("use_index", True),
-            ("title", None),
-            ("grid", None),
-            ("legend", True),
-            ("style", None),
-            ("logx", False),
-            ("logy", False),
-            ("loglog", False),
-            ("xticks", None),
-            ("yticks", None),
-            ("xlim", None),
-            ("ylim", None),
-            ("rot", None),
-            ("fontsize", None),
-            ("colormap", None),
-            ("table", False),
-            ("yerr", None),
-            ("xerr", None),
-            ("secondary_y", False),
-            ("sort_columns", False),
-        }
-    # removing keys that are not required
-    kwargs.pop("self", None)
-    kwargs.pop("kwds", None)
-    kwargs.pop("kind", None)
-
-    for arg, def_val in positional_args:
-        # map the argument if possible
-        if backend_name in args_map:
-            if arg in kwargs and arg in args_map[backend_name]:
-                kwargs[args_map[backend_name][arg]] = kwargs.pop(arg)
-        # remove argument is default and not mapped
-        if arg in kwargs and kwargs[arg] == def_val:
-            kwargs.pop(arg, None)
-    return data_preprocessor_map[kind](data), kwargs
-
-
-class KoalasSeriesPlotMethods(PandasObject):
-    """
-    Series plotting accessor and method.
+    Series/Frames plotting accessor and method.
+
+    Uses the backend specified by the
+    option ``plotting.backend``. By default, matplotlib is used.
 
     Plotting methods can also be accessed by calling the accessor as a method
     with the ``kind`` argument:
     ``s.plot(kind='hist')`` is equivalent to ``s.plot.hist()``
     """
 
+    # from databricks.koalas import DataFrame, Series
+
+    _common_kinds = {"area", "bar", "barh", "box", "hist", "kde", "line", "pie"}
+    _series_kinds = _common_kinds.union(set([]))
+    _dataframe_kinds = _common_kinds.union(set(["scatter", "hexbin"]))
+    _kind_aliases = {"density": "kde"}
+    _koalas_all_kinds = _common_kinds.union(_series_kinds).union(_dataframe_kinds)
+
     def __init__(self, data):
         self.data = data
 
-    def __call__(
-        self,
-        kind="line",
-        ax=None,
-        figsize=None,
-        use_index=True,
-        title=None,
-        grid=None,
-        legend=False,
-        style=None,
-        logx=False,
-        logy=False,
-        loglog=False,
-        xticks=None,
-        yticks=None,
-        xlim=None,
-        ylim=None,
-        rot=None,
-        fontsize=None,
-        colormap=None,
-        table=False,
-        yerr=None,
-        xerr=None,
-        label=None,
-        secondary_y=False,
-        **kwds
-    ):
+    @staticmethod
+    def _format_args(backend_name, data, kind, kwargs):
+        """
+        Format the arguments.
+        Assigns default values to plotting functions.
+        Maps the argument to the appropriate backend.
+        """
+        from databricks.koalas import DataFrame, Series
+
+        data_preprocessor_map = {
+            "pie": TopNPlot().get_top_n,
+            "bar": TopNPlot().get_top_n,
+            "barh": TopNPlot().get_top_n,
+            "scatter": TopNPlot().get_top_n,
+            "area": SampledPlot().get_sampled,
+            "line": SampledPlot().get_sampled,
+        }
+        # make the arguments values of matplotlib compatible with that of plotting backend
+        args_map = {
+            "plotly": {
+                "logx": "log_x",
+                "logy": "log_y",
+                "xlim": "range_x",
+                "ylim": "range_y",
+                "yerr": "error_y",
+                "xerr": "error_x",
+            }
+        }
+
+        if isinstance(data, Series):
+            positional_args = {
+                ("ax", None),
+                ("figsize", None),
+                ("use_index", True),
+                ("title", None),
+                ("grid", None),
+                ("legend", False),
+                ("style", None),
+                ("logx", False),
+                ("logy", False),
+                ("loglog", False),
+                ("xticks", None),
+                ("yticks", None),
+                ("xlim", None),
+                ("ylim", None),
+                ("rot", None),
+                ("fontsize", None),
+                ("colormap", None),
+                ("table", False),
+                ("yerr", None),
+                ("xerr", None),
+                ("label", None),
+                ("secondary_y", False),
+            }
+        elif isinstance(data, DataFrame):
+            positional_args = {
+                ("x", None),
+                ("y", None),
+                ("ax", None),
+                ("subplots", None),
+                ("sharex", None),
+                ("sharey", False),
+                ("layout", None),
+                ("figsize", None),
+                ("use_index", True),
+                ("title", None),
+                ("grid", None),
+                ("legend", True),
+                ("style", None),
+                ("logx", False),
+                ("logy", False),
+                ("loglog", False),
+                ("xticks", None),
+                ("yticks", None),
+                ("xlim", None),
+                ("ylim", None),
+                ("rot", None),
+                ("fontsize", None),
+                ("colormap", None),
+                ("table", False),
+                ("yerr", None),
+                ("xerr", None),
+                ("secondary_y", False),
+                ("sort_columns", False),
+            }
+        # removing keys that are not required
+        attrs_to_ignore = ["self", "kind", "data", "data", "kwargs", "backend"]
+        # no need to map and remove anything  if the backend is the default
+        for temp in attrs_to_ignore:
+            kwargs.pop(temp, None)
+
+        for arg, def_val in positional_args:
+            # map the argument if possible
+            if backend_name != "databricks.koalas.plot":
+                if backend_name in args_map:
+                    if arg in kwargs and arg in args_map[backend_name]:
+                        kwargs[args_map[backend_name][arg]] = kwargs.pop(arg)
+                # remove argument is default and not mapped
+                if arg in kwargs and kwargs[arg] == def_val:
+                    kwargs.pop(arg, None)
+            else:
+                if arg not in kwargs:
+                    kwargs[arg] = def_val
+
+        if backend_name != "databricks.koalas.plot":
+            data = data_preprocessor_map[kind](data)
+
+        return data, kwargs
+
+    def __call__(self, kind="line", backend="matplotlib", **kwargs):
+
         positional_args = locals()
-        plot_backend = _get_plot_backend(kwds.pop("backend", None))
+        plot_backend = _get_plot_backend(backend)
+        args = {**positional_args, **kwargs}
         # when using another backend, let the backend take the charge
+        plot_data, kwds = self._format_args(plot_backend.__name__, self.data, kind, args)
+
         if plot_backend.__name__ != "databricks.koalas.plot":
-            args = {**positional_args, **kwds}
-            plot_data, kwds = _get_args_map(plot_backend.__name__, self.data, kind, args)
             return plot_backend.plot(plot_data, kind=kind, **kwds)
 
-        return plot_series(
-            self.data,
-            kind=kind,
-            ax=ax,
-            figsize=figsize,
-            use_index=use_index,
-            title=title,
-            grid=grid,
-            legend=legend,
-            style=style,
-            logx=logx,
-            logy=logy,
-            loglog=loglog,
-            xticks=xticks,
-            yticks=yticks,
-            xlim=xlim,
-            ylim=ylim,
-            rot=rot,
-            fontsize=fontsize,
-            colormap=colormap,
-            table=table,
-            yerr=yerr,
-            xerr=xerr,
-            label=label,
-            secondary_y=secondary_y,
-            **kwds
-        )
+        if kind not in self._koalas_all_kinds:
+            raise ValueError("{} is not a valid plot kind".format(kind))
+
+        from databricks.koalas import DataFrame, Series
 
-    __call__.__doc__ = plot_series.__doc__
+        if isinstance(self.data, Series):
+            if kind not in self._series_kinds:
+                return unsupported_function(class_name="pd.Series", method_name=kind)()
+            return plot_series(data=self.data, kind=kind, **kwds)
+        elif isinstance(self.data, DataFrame):
+            if kind not in self._dataframe_kinds:
+                return unsupported_function(class_name="pd.DataFrame", method_name=kind)()
+            return plot_frame(data=self.data, kind=kind, **kwds)
 
     def line(self, x=None, y=None, **kwargs):
         """
-        Plot Series as lines.
+        Plot DataFrame/Series as lines.
 
         This function is useful to plot lines using Series's values
         as coordinates.
@@ -1376,12 +1361,14 @@ def line(self, x=None, y=None, **kwargs):
             Either the location or the label of the columns to be used.
             By default, it will use the remaining DataFrame numeric columns.
         **kwds
-            Keyword arguments to pass on to :meth:`Series.plot`.
+            Keyword arguments to pass on to :meth:`Series.plot` or :meth:`DataFrame.plot`.
 
         Returns
         -------
-        :class:`matplotlib.axes.Axes` or :class:`numpy.ndarray`
+        axes : :class:`matplotlib.axes.Axes` or :class:`numpy.ndarray`
             Return an ndarray when ``subplots=True``.
+            Return an custom object when ``backend!=matplotlib``.
+
 
         See Also
         --------
@@ -1391,41 +1378,136 @@ def line(self, x=None, y=None, **kwargs):
         --------
         Basic plot.
 
+        For Series:
+
         .. plot::
             :context: close-figs
 
             >>> s = ks.Series([1, 3, 2])
             >>> ax = s.plot.line()
+
+        For DataFrame:
+
+        .. plot::
+            :context: close-figs
+
+            The following example shows the populations for some animals
+            over the years.
+
+            >>> df = ks.DataFrame({'pig': [20, 18, 489, 675, 1776],
+            ...                    'horse': [4, 25, 281, 600, 1900]},
+            ...                   index=[1990, 1997, 2003, 2009, 2014])
+            >>> lines = df.plot.line()
+
+        .. plot::
+            :context: close-figs
+
+            An example with subplots, so an array of axes is returned.
+
+            >>> axes = df.plot.line(subplots=True)
+            >>> type(axes)
+            <class 'numpy.ndarray'>
+
+        .. plot::
+            :context: close-figs
+
+            The following example shows the relationship between both
+            populations.
+
+            >>> lines = df.plot.line(x='pig', y='horse')
         """
         return self(kind="line", x=x, y=y, **kwargs)
 
-    def bar(self, **kwds):
+    def bar(self, x=None, y=None, **kwds):
         """
         Vertical bar plot.
 
         Parameters
         ----------
+        x : label or position, optional
+            Allows plotting of one column versus another.
+            If not specified, the index of the DataFrame is used.
+        y : label or position, optional
+            Allows plotting of one column versus another.
+            If not specified, all numerical columns are used.
         **kwds : optional
             Additional keyword arguments are documented in
-            :meth:`Koalas.Series.plot`.
+            :meth:`Koalas.Series.plot` or :meth:`Koalas.DataFrame.plot`.
 
         Returns
         -------
-        axes : :class:`matplotlib.axes.Axes` or numpy.ndarray of them
+        axes : :class:`matplotlib.axes.Axes` or :class:`numpy.ndarray`
+            Return an ndarray when ``subplots=True``.
+            Return an custom object when ``backend!=matplotlib``.
+
 
         Examples
         --------
         Basic plot.
 
+        For Series:
+
         .. plot::
             :context: close-figs
 
             >>> s = ks.Series([1, 3, 2])
             >>> ax = s.plot.bar()
+
+        For DataFrame:
+
+        .. plot::
+            :context: close-figs
+
+            >>> df = ks.DataFrame({'lab': ['A', 'B', 'C'], 'val': [10, 30, 20]})
+            >>> ax = df.plot.bar(x='lab', y='val', rot=0)
+
+        Plot a whole dataframe to a bar plot. Each column is assigned a
+        distinct color, and each row is nested in a group along the
+        horizontal axis.
+
+        .. plot::
+            :context: close-figs
+
+            >>> speed = [0.1, 17.5, 40, 48, 52, 69, 88]
+            >>> lifespan = [2, 8, 70, 1.5, 25, 12, 28]
+            >>> index = ['snail', 'pig', 'elephant',
+            ...          'rabbit', 'giraffe', 'coyote', 'horse']
+            >>> df = ks.DataFrame({'speed': speed,
+            ...                    'lifespan': lifespan}, index=index)
+            >>> ax = df.plot.bar(rot=0)
+
+        Instead of nesting, the figure can be split by column with
+        ``subplots=True``. In this case, a :class:`numpy.ndarray` of
+        :class:`matplotlib.axes.Axes` are returned.
+
+        .. plot::
+            :context: close-figs
+
+            >>> axes = df.plot.bar(rot=0, subplots=True)
+            >>> axes[1].legend(loc=2)  # doctest: +SKIP
+
+        Plot a single column.
+
+        .. plot::
+            :context: close-figs
+
+            >>> ax = df.plot.bar(y='speed', rot=0)
+
+        Plot only selected categories for the DataFrame.
+
+        .. plot::
+            :context: close-figs
+
+            >>> ax = df.plot.bar(x='lifespan', rot=0)
         """
-        return self(kind="bar", **kwds)
+        from databricks.koalas import DataFrame, Series
+
+        if isinstance(self.data, Series):
+            return self(kind="bar", **kwds)
+        elif isinstance(self.data, DataFrame):
+            return self(kind="bar", x=x, y=y, **kwds)
 
-    def barh(self, **kwds):
+    def barh(self, x=None, y=None, **kwargs):
         """
         Make a horizontal bar plot.
 
@@ -1442,7 +1524,8 @@ def barh(self, **kwds):
         y : label or position, default All numeric columns in dataframe
             Columns to be plotted from the DataFrame.
         **kwds
-            Keyword arguments to pass on to :meth:`databricks.koalas.DataFrame.plot`.
+            Keyword arguments to pass on to
+            :meth:`databricks.koalas.DataFrame.plot` or :meth:`databricks.koalas.Series.plot`.
 
         Returns
         -------
@@ -1454,17 +1537,71 @@ def barh(self, **kwds):
 
         Examples
         --------
+        For Series:
+
         .. plot::
             :context: close-figs
 
             >>> df = ks.DataFrame({'lab': ['A', 'B', 'C'], 'val': [10, 30, 20]})
             >>> plot = df.val.plot.barh()
+
+        For DataFrame:
+
+        .. plot::
+            :context: close-figs
+
+            >>> df = ks.DataFrame({'lab': ['A', 'B', 'C'], 'val': [10, 30, 20]})
+            >>> ax = df.plot.barh(x='lab', y='val')
+
+        Plot a whole DataFrame to a horizontal bar plot
+
+        .. plot::
+            :context: close-figs
+
+            >>> speed = [0.1, 17.5, 40, 48, 52, 69, 88]
+            >>> lifespan = [2, 8, 70, 1.5, 25, 12, 28]
+            >>> index = ['snail', 'pig', 'elephant',
+            ...          'rabbit', 'giraffe', 'coyote', 'horse']
+            >>> df = ks.DataFrame({'speed': speed,
+            ...                    'lifespan': lifespan}, index=index)
+            >>> ax = df.plot.barh()
+
+        Plot a column of the DataFrame to a horizontal bar plot
+
+        .. plot::
+            :context: close-figs
+
+            >>> speed = [0.1, 17.5, 40, 48, 52, 69, 88]
+            >>> lifespan = [2, 8, 70, 1.5, 25, 12, 28]
+            >>> index = ['snail', 'pig', 'elephant',
+            ...          'rabbit', 'giraffe', 'coyote', 'horse']
+            >>> df = ks.DataFrame({'speed': speed,
+            ...                    'lifespan': lifespan}, index=index)
+            >>> ax = df.plot.barh(y='speed')
+
+        Plot DataFrame versus the desired column
+
+        .. plot::
+            :context: close-figs
+
+            >>> speed = [0.1, 17.5, 40, 48, 52, 69, 88]
+            >>> lifespan = [2, 8, 70, 1.5, 25, 12, 28]
+            >>> index = ['snail', 'pig', 'elephant',
+            ...          'rabbit', 'giraffe', 'coyote', 'horse']
+            >>> df = ks.DataFrame({'speed': speed,
+            ...                    'lifespan': lifespan}, index=index)
+            >>> ax = df.plot.barh(x='lifespan')
         """
-        return self(kind="barh", **kwds)
+        from databricks.koalas import DataFrame, Series
+
+        if isinstance(self.data, Series):
+            return self(kind="barh", **kwargs)
+        elif isinstance(self.data, DataFrame):
+            return self(kind="barh", x=x, y=y, **kwargs)
 
     def box(self, **kwds):
         """
-        Make a box plot of the DataFrame columns.
+        Make a box plot of the Series columns.
 
         Parameters
         ----------
@@ -1479,7 +1616,9 @@ def box(self, **kwds):
 
         Returns
         -------
-        axes : :class:`matplotlib.axes.Axes` or numpy.ndarray of them
+        axes : :class:`matplotlib.axes.Axes` or :class:`numpy.ndarray`
+            Return an ndarray when ``subplots=True``.
+            Return an custom object when ``backend!=matplotlib``.
 
         Notes
         -----
@@ -1498,40 +1637,76 @@ def box(self, **kwds):
         Draw a box plot from a DataFrame with four columns of randomly
         generated data.
 
+        For Series:
+
         .. plot::
             :context: close-figs
 
             >>> data = np.random.randn(25, 4)
             >>> df = ks.DataFrame(data, columns=list('ABCD'))
             >>> ax = df['A'].plot.box()
+
+        This is an unsupported function for DataFrame type
         """
-        return self(kind="box", **kwds)
+        from databricks.koalas import DataFrame, Series
+
+        if isinstance(self.data, Series):
+            return self(kind="box", **kwds)
+        elif isinstance(self.data, DataFrame):
+            return unsupported_function(class_name="pd.DataFrame", method_name="box")()
 
     def hist(self, bins=10, **kwds):
         """
         Draw one histogram of the DataFrame’s columns.
+        A `histogram`_ is a representation of the distribution of data.
+        This function calls :meth:`matplotlib.pyplot.hist` or :meth:`plotting.backend.plot`,
+        on each series in the DataFrame, resulting in one histogram per column.
+
+        .. _histogram: https://en.wikipedia.org/wiki/Histogram
+
 
         Parameters
         ----------
-        bins : integer, default 10
-            Number of histogram bins to be used
-        **kwds : optional
-            Additional keyword arguments are documented in
-            :meth:`Koalas.Series.plot`.
+        bins : integer or sequence, default 10
+            Number of histogram bins to be used. If an integer is given, bins + 1
+            bin edges are calculated and returned. If bins is a sequence, gives
+            bin edges, including left edge of first bin and right edge of last
+            bin. In this case, bins is returned unmodified.
+        **kwds
+            All other plotting keyword arguments to be passed to
+            :meth:`matplotlib.pyplot.hist`
+            Koalas.Series.plot.
 
         Returns
         -------
-        axes : :class:`matplotlib.axes.Axes` or numpy.ndarray of them
+        axes : :class:`matplotlib.axes.Axes` or :class:`numpy.ndarray`
+            Return an ndarray when ``subplots=True``.
+            Return an custom object when ``backend!=matplotlib``.
+
 
         Examples
         --------
         Basic plot.
 
+        For Series:
+
         .. plot::
             :context: close-figs
 
             >>> s = ks.Series([1, 3, 2])
             >>> ax = s.plot.hist()
+
+        For DataFrame:
+
+        .. plot::
+            :context: close-figs
+
+            >>> df = pd.DataFrame(
+            ...     np.random.randint(1, 7, 6000),
+            ...     columns=['one'])
+            >>> df['two'] = df['one'] + np.random.randint(1, 7, 6000)
+            >>> df = ks.from_pandas(df)
+            >>> ax = df.plot.hist(bins=12, alpha=0.5)
         """
         return self(kind="hist", bins=bins, **kwds)
 
@@ -1554,7 +1729,9 @@ def kde(self, bw_method=None, ind=None, **kwargs):
 
         Returns
         -------
-        matplotlib.axes.Axes or numpy.ndarray of them
+        axes : :class:`matplotlib.axes.Axes` or :class:`numpy.ndarray`
+            Return an ndarray when ``subplots=True``.
+            Return an custom object when ``backend!=matplotlib``.
 
         Examples
         --------
@@ -1580,12 +1757,33 @@ def kde(self, bw_method=None, ind=None, **kwargs):
             :context: close-figs
 
             >>> ax = s.plot.kde(ind=[1, 2, 3, 4, 5], bw_method=0.3)
+
+        For DataFrame, it works in the same way as Series:
+
+        .. plot::
+            :context: close-figs
+
+            >>> df = ks.DataFrame({
+            ...     'x': [1, 2, 2.5, 3, 3.5, 4, 5],
+            ...     'y': [4, 4, 4.5, 5, 5.5, 6, 6],
+            ... })
+            >>> ax = df.plot.kde(bw_method=0.3)
+
+        .. plot::
+            :context: close-figs
+
+            >>> ax = df.plot.kde(bw_method=3)
+
+        .. plot::
+            :context: close-figs
+
+            >>> ax = df.plot.kde(ind=[1, 2, 3, 4, 5, 6], bw_method=0.3)
         """
         return self(kind="kde", bw_method=bw_method, ind=ind, **kwargs)
 
     density = kde
 
-    def area(self, **kwds):
+    def area(self, x=None, y=None, stacked=True, **kwds):
         """
         Draw a stacked area plot.
 
@@ -1607,12 +1805,15 @@ def area(self, **kwds):
 
         Returns
         -------
-        matplotlib.axes.Axes or numpy.ndarray
-            Area plot, or array of area plots if subplots is True.
+        axes : :class:`matplotlib.axes.Axes` or :class:`numpy.ndarray`
+            Return an ndarray when ``subplots=True``.
+            Return an custom object when ``backend!=matplotlib``.
 
         Examples
         --------
 
+        For Series
+
         .. plot::
             :context: close-figs
 
@@ -1623,10 +1824,28 @@ def area(self, **kwds):
             ... }, index=pd.date_range(start='2018/01/01', end='2018/07/01',
             ...                        freq='M'))
             >>> plot = df.sales.plot.area()
+
+        For DataFrame
+
+        .. plot::
+            :context: close-figs
+
+            >>> df = ks.DataFrame({
+            ...     'sales': [3, 2, 3, 9, 10, 6],
+            ...     'signups': [5, 5, 6, 12, 14, 13],
+            ...     'visits': [20, 42, 28, 62, 81, 50],
+            ... }, index=pd.date_range(start='2018/01/01', end='2018/07/01',
+            ...                        freq='M'))
+            >>> plot = df.plot.area()
         """
-        return self(kind="area", **kwds)
+        from databricks.koalas import DataFrame, Series
 
-    def pie(self, **kwds):
+        if isinstance(self.data, Series):
+            return self(kind="area", **kwds)
+        elif isinstance(self.data, DataFrame):
+            return self(kind="area", x=x, y=y, stacked=stacked, **kwds)
+
+    def pie(self, y=None, **kwds):
         """
         Generate a pie plot.
 
@@ -1652,506 +1871,37 @@ def pie(self, **kwds):
         Examples
         --------
 
+        For Series:
+
         .. plot::
             :context: close-figs
 
             >>> df = ks.DataFrame({'mass': [0.330, 4.87, 5.97],
             ...                    'radius': [2439.7, 6051.8, 6378.1]},
             ...                   index=['Mercury', 'Venus', 'Earth'])
-            >>> plot = df.mass.plot.pie(figsize=(5, 5))
+            >>> plot = df.mass.plot.pie(subplots=True, figsize=(6, 3))
+
+
+        For DataFrame:
 
         .. plot::
             :context: close-figs
 
-            >>> plot = df.mass.plot.pie(subplots=True, figsize=(6, 3))
+            >>> plot = df.plot.pie(y='mass', figsize=(5, 5))
         """
-        return self(kind="pie", **kwds)
+        from databricks.koalas import DataFrame, Series
 
+        if isinstance(self.data, Series):
+            return self(kind="pie", **kwds)
+        else:
+            # pandas will raise an error if y is None and subplots if not True
+            if isinstance(self.data, DataFrame) and y is None and not kwds.get("subplots", False):
+                raise ValueError("pie requires either y column or 'subplots=True'")
+            return self(kind="pie", y=y, **kwds)
 
-class KoalasFramePlotMethods(PandasObject):
-    # TODO: not sure if Koalas wants to combine plot method for Series and DataFrame
-    """
-    DataFrame plotting accessor and method.
-
-    Plotting methods can also be accessed by calling the accessor as a method
-    with the ``kind`` argument:
-    ``df.plot(kind='hist')`` is equivalent to ``df.plot.hist()``
-    """
-
-    def __init__(self, data):
-        self.data = data
-
-    def __call__(
-        self,
-        x=None,
-        y=None,
-        kind="line",
-        ax=None,
-        subplots=None,
-        sharex=None,
-        sharey=False,
-        layout=None,
-        figsize=None,
-        use_index=True,
-        title=None,
-        grid=None,
-        legend=True,
-        style=None,
-        logx=False,
-        logy=False,
-        loglog=False,
-        xticks=None,
-        yticks=None,
-        xlim=None,
-        ylim=None,
-        rot=None,
-        fontsize=None,
-        colormap=None,
-        table=False,
-        yerr=None,
-        xerr=None,
-        secondary_y=False,
-        sort_columns=False,
-        **kwds
-    ):
-        positional_args = locals()
-        plot_backend = _get_plot_backend(kwds.pop("backend", None))
-        # when using another backend, let the backend take the charge
-        if plot_backend.__name__ != "databricks.koalas.plot":
-            args = {**positional_args, **kwds}
-            plot_data, kwds = _get_args_map(plot_backend.__name__, self.data, kind, args)
-            return plot_backend.plot(plot_data, kind=kind, **kwds)
-
-        return plot_frame(
-            self.data,
-            x=x,
-            y=y,
-            kind=kind,
-            ax=ax,
-            subplots=subplots,
-            sharex=sharex,
-            sharey=sharey,
-            layout=layout,
-            figsize=figsize,
-            use_index=use_index,
-            title=title,
-            grid=grid,
-            legend=legend,
-            style=style,
-            logx=logx,
-            logy=logy,
-            loglog=loglog,
-            xticks=xticks,
-            yticks=yticks,
-            xlim=xlim,
-            ylim=ylim,
-            rot=rot,
-            fontsize=fontsize,
-            colormap=colormap,
-            table=table,
-            yerr=yerr,
-            xerr=xerr,
-            secondary_y=secondary_y,
-            sort_columns=sort_columns,
-            **kwds
-        )
-
-    def line(self, x=None, y=None, **kwargs):
-        """
-        Plot DataFrame as lines.
-
-        Parameters
-        ----------
-        x: int or str, optional
-            Columns to use for the horizontal axis.
-        y : int, str, or list of them, optional
-            The values to be plotted.
-        **kwargs
-            Keyword arguments to pass on to :meth:`DataFrame.plot`.
-
-        Returns
-        -------
-        :class:`matplotlib.axes.Axes` or :class:`numpy.ndarray`
-            Return an ndarray when ``subplots=True``.
-
-        See Also
-        --------
-        matplotlib.pyplot.plot : Plot y versus x as lines and/or markers.
-
-        Examples
-        --------
-
-        .. plot::
-            :context: close-figs
-
-            The following example shows the populations for some animals
-            over the years.
-
-            >>> df = ks.DataFrame({'pig': [20, 18, 489, 675, 1776],
-            ...                    'horse': [4, 25, 281, 600, 1900]},
-            ...                   index=[1990, 1997, 2003, 2009, 2014])
-            >>> lines = df.plot.line()
-
-        .. plot::
-            :context: close-figs
-
-            An example with subplots, so an array of axes is returned.
-
-            >>> axes = df.plot.line(subplots=True)
-            >>> type(axes)
-            <class 'numpy.ndarray'>
-
-        .. plot::
-            :context: close-figs
-
-            The following example shows the relationship between both
-            populations.
-
-            >>> lines = df.plot.line(x='pig', y='horse')
-        """
-        return self(kind="line", x=x, y=y, **kwargs)
-
-    def kde(self, bw_method=None, ind=None, **kwargs):
-        """
-        Generate Kernel Density Estimate plot using Gaussian kernels.
-
-        Parameters
-        ----------
-        bw_method : scalar
-            The method used to calculate the estimator bandwidth.
-            See KernelDensity in PySpark for more information.
-        ind : NumPy array or integer, optional
-            Evaluation points for the estimated PDF. If None (default),
-            1000 equally spaced points are used. If `ind` is a NumPy array, the
-            KDE is evaluated at the points passed. If `ind` is an integer,
-            `ind` number of equally spaced points are used.
-        **kwargs : optional
-            Keyword arguments to pass on to :meth:`Koalas.DataFrame.plot`.
-
-        Returns
-        -------
-        matplotlib.axes.Axes or numpy.ndarray of them
-
-        Examples
-        --------
-        For DataFrame, it works in the same way as Series:
-
-        .. plot::
-            :context: close-figs
-
-            >>> df = ks.DataFrame({
-            ...     'x': [1, 2, 2.5, 3, 3.5, 4, 5],
-            ...     'y': [4, 4, 4.5, 5, 5.5, 6, 6],
-            ... })
-            >>> ax = df.plot.kde(bw_method=0.3)
-
-        .. plot::
-            :context: close-figs
-
-            >>> ax = df.plot.kde(bw_method=3)
-
-        .. plot::
-            :context: close-figs
-
-            >>> ax = df.plot.kde(ind=[1, 2, 3, 4, 5, 6], bw_method=0.3)
-        """
-        return self(kind="kde", bw_method=bw_method, ind=ind, **kwargs)
-
-    density = kde
-
-    def pie(self, y=None, **kwds):
-        """
-        Generate a pie plot.
-        A pie plot is a proportional representation of the numerical data in a
-        column. This function wraps :meth:`matplotlib.pyplot.pie` for the
-        specified column. If no column reference is passed and
-        ``subplots=True`` a pie plot is drawn for each numerical column
-        independently.
-
-        Parameters
-        ----------
-        y : int or label, optional
-            Label or position of the column to plot.
-            If not provided, ``subplots=True`` argument must be passed.
-        **kwds
-            Keyword arguments to pass on to :meth:`Koalas.DataFrame.plot`.
-
-        Returns
-        -------
-        matplotlib.axes.Axes or np.ndarray of them
-            A NumPy array is returned when `subplots` is True.
-
-        Examples
-        --------
-        In the example below we have a DataFrame with the information about
-        planet's mass and radius. We pass the the 'mass' column to the
-        pie function to get a pie plot.
-
-        .. plot::
-            :context: close-figs
-
-            >>> df = ks.DataFrame({'mass': [0.330, 4.87, 5.97],
-            ...                    'radius': [2439.7, 6051.8, 6378.1]},
-            ...                   index=['Mercury', 'Venus', 'Earth'])
-            >>> plot = df.plot.pie(y='mass', figsize=(5, 5))
-
-        .. plot::
-            :context: close-figs
-
-            >>> plot = df.plot.pie(subplots=True, figsize=(6, 3))
-        """
-        from databricks.koalas import DataFrame
-
-        # pandas will raise an error if y is None and subplots if not True
-        if isinstance(self.data, DataFrame) and y is None and not kwds.get("subplots", False):
-            raise ValueError("pie requires either y column or 'subplots=True'")
-        return self(kind="pie", y=y, **kwds)
-
-    def area(self, x=None, y=None, stacked=True, **kwds):
-        """
-        Draw a stacked area plot.
-
-        An area plot displays quantitative data visually.
-        This function wraps the matplotlib area function.
-
-        Parameters
-        ----------
-        x : label or position, optional
-            Coordinates for the X axis. By default uses the index.
-        y : label or position, optional
-            Column to plot. By default uses all columns.
-        stacked : bool, default True
-            Area plots are stacked by default. Set to False to create a
-            unstacked plot.
-        **kwds : optional
-            Additional keyword arguments are documented in
-            :meth:`DataFrame.plot`.
-
-        Returns
-        -------
-        matplotlib.axes.Axes or numpy.ndarray
-            Area plot, or array of area plots if subplots is True.
-
-        Examples
-        --------
-
-        .. plot::
-            :context: close-figs
-
-            >>> df = ks.DataFrame({
-            ...     'sales': [3, 2, 3, 9, 10, 6],
-            ...     'signups': [5, 5, 6, 12, 14, 13],
-            ...     'visits': [20, 42, 28, 62, 81, 50],
-            ... }, index=pd.date_range(start='2018/01/01', end='2018/07/01',
-            ...                        freq='M'))
-            >>> plot = df.plot.area()
-        """
-        return self(kind="area", x=x, y=y, stacked=stacked, **kwds)
-
-    def bar(self, x=None, y=None, **kwds):
-        """
-        Vertical bar plot.
-
-        Parameters
-        ----------
-        x : label or position, optional
-            Allows plotting of one column versus another.
-            If not specified, the index of the DataFrame is used.
-        y : label or position, optional
-            Allows plotting of one column versus another.
-            If not specified, all numerical columns are used.
-        **kwds : optional
-            Additional keyword arguments are documented in
-            :meth:`Koalas.DataFrame.plot`.
-
-        Returns
-        -------
-        axes : :class:`matplotlib.axes.Axes` or numpy.ndarray of them
-
-        Examples
-        --------
-        Basic plot.
-
-        .. plot::
-            :context: close-figs
-
-            >>> df = ks.DataFrame({'lab': ['A', 'B', 'C'], 'val': [10, 30, 20]})
-            >>> ax = df.plot.bar(x='lab', y='val', rot=0)
-
-        Plot a whole dataframe to a bar plot. Each column is assigned a
-        distinct color, and each row is nested in a group along the
-        horizontal axis.
-
-        .. plot::
-            :context: close-figs
-
-            >>> speed = [0.1, 17.5, 40, 48, 52, 69, 88]
-            >>> lifespan = [2, 8, 70, 1.5, 25, 12, 28]
-            >>> index = ['snail', 'pig', 'elephant',
-            ...          'rabbit', 'giraffe', 'coyote', 'horse']
-            >>> df = ks.DataFrame({'speed': speed,
-            ...                    'lifespan': lifespan}, index=index)
-            >>> ax = df.plot.bar(rot=0)
-
-        Instead of nesting, the figure can be split by column with
-        ``subplots=True``. In this case, a :class:`numpy.ndarray` of
-        :class:`matplotlib.axes.Axes` are returned.
-
-        .. plot::
-            :context: close-figs
-
-            >>> axes = df.plot.bar(rot=0, subplots=True)
-            >>> axes[1].legend(loc=2)  # doctest: +SKIP
-
-        Plot a single column.
-
-        .. plot::
-            :context: close-figs
-
-            >>> ax = df.plot.bar(y='speed', rot=0)
-
-        Plot only selected categories for the DataFrame.
-
-        .. plot::
-            :context: close-figs
-
-            >>> ax = df.plot.bar(x='lifespan', rot=0)
-        """
-        return self(kind="bar", x=x, y=y, **kwds)
-
-    def barh(self, x=None, y=None, **kwargs):
-        """
-        Make a horizontal bar plot.
-
-        A horizontal bar plot is a plot that presents quantitative data with rectangular
-        bars with lengths proportional to the values that they represent. A bar plot shows
-        comparisons among discrete categories. One axis of the plot shows the specific
-        categories being compared, and the other axis represents a measured value.
-
-        Parameters
-        ----------
-        x : label or position, default DataFrame.index
-            Column to be used for categories.
-        y : label or position, default All numeric columns in dataframe
-            Columns to be plotted from the DataFrame.
-        **kwds:
-            Keyword arguments to pass on to :meth:`databricks.koalas.DataFrame.plot`.
-
-        Returns
-        -------
-        :class:`matplotlib.axes.Axes` or numpy.ndarray of them
-
-        See Also
-        --------
-        matplotlib.axes.Axes.bar : Plot a vertical bar plot using matplotlib.
-
-        Examples
-        --------
-        Basic example
-
-        .. plot::
-            :context: close-figs
-
-            >>> df = ks.DataFrame({'lab': ['A', 'B', 'C'], 'val': [10, 30, 20]})
-            >>> ax = df.plot.barh(x='lab', y='val')
-
-        Plot a whole DataFrame to a horizontal bar plot
-
-        .. plot::
-            :context: close-figs
-
-            >>> speed = [0.1, 17.5, 40, 48, 52, 69, 88]
-            >>> lifespan = [2, 8, 70, 1.5, 25, 12, 28]
-            >>> index = ['snail', 'pig', 'elephant',
-            ...          'rabbit', 'giraffe', 'coyote', 'horse']
-            >>> df = ks.DataFrame({'speed': speed,
-            ...                    'lifespan': lifespan}, index=index)
-            >>> ax = df.plot.barh()
-
-        Plot a column of the DataFrame to a horizontal bar plot
-
-        .. plot::
-            :context: close-figs
-
-            >>> speed = [0.1, 17.5, 40, 48, 52, 69, 88]
-            >>> lifespan = [2, 8, 70, 1.5, 25, 12, 28]
-            >>> index = ['snail', 'pig', 'elephant',
-            ...          'rabbit', 'giraffe', 'coyote', 'horse']
-            >>> df = ks.DataFrame({'speed': speed,
-            ...                    'lifespan': lifespan}, index=index)
-            >>> ax = df.plot.barh(y='speed')
-
-        Plot DataFrame versus the desired column
-
-        .. plot::
-            :context: close-figs
-
-            >>> speed = [0.1, 17.5, 40, 48, 52, 69, 88]
-            >>> lifespan = [2, 8, 70, 1.5, 25, 12, 28]
-            >>> index = ['snail', 'pig', 'elephant',
-            ...          'rabbit', 'giraffe', 'coyote', 'horse']
-            >>> df = ks.DataFrame({'speed': speed,
-            ...                    'lifespan': lifespan}, index=index)
-            >>> ax = df.plot.barh(x='lifespan')
-        """
-        return self(kind="barh", x=x, y=y, **kwargs)
-
-    def hexbin(self, **kwds):
-        return unsupported_function(class_name="pd.DataFrame", method_name="hexbin")()
-
-    def box(self, **kwds):
-        return unsupported_function(class_name="pd.DataFrame", method_name="box")()
-
-    def hist(self, bins=10, **kwds):
-        """
-        Make a histogram of the DataFrame's.
-        A `histogram`_ is a representation of the distribution of data.
-        This function calls :meth:`matplotlib.pyplot.hist`, on each series in
-        the DataFrame, resulting in one histogram per column.
-
-        .. _histogram: https://en.wikipedia.org/wiki/Histogram
-
-        Parameters
-        ----------
-        bins : integer or sequence, default 10
-            Number of histogram bins to be used. If an integer is given, bins + 1
-            bin edges are calculated and returned. If bins is a sequence, gives
-            bin edges, including left edge of first bin and right edge of last
-            bin. In this case, bins is returned unmodified.
-        **kwds
-            All other plotting keyword arguments to be passed to
-            :meth:`matplotlib.pyplot.hist`.
-
-        Returns
-        -------
-        matplotlib.AxesSubplot or numpy.ndarray of them
-
-        See Also
-        --------
-        matplotlib.pyplot.hist : Plot a histogram using matplotlib.
-
-        Examples
-        --------
-        When we draw a dice 6000 times, we expect to get each value around 1000
-        times. But when we draw two dices and sum the result, the distribution
-        is going to be quite different. A histogram illustrates those
-        distributions.
-
-        .. plot::
-            :context: close-figs
-
-            >>> df = pd.DataFrame(
-            ...     np.random.randint(1, 7, 6000),
-            ...     columns=['one'])
-            >>> df['two'] = df['one'] + np.random.randint(1, 7, 6000)
-            >>> df = ks.from_pandas(df)
-            >>> ax = df.plot.hist(bins=12, alpha=0.5)
-        """
-        return self(kind="hist", bins=bins, **kwds)
-
-    def scatter(self, x, y, s=None, c=None, **kwds):
-        """
-        Create a scatter plot with varying marker point size and color.
+    def scatter(self, x, y, s=None, c=None, **kwds):
+        """
+        Create a scatter plot with varying marker point size and color.
 
         The coordinates of each point are defined by two dataframe columns and
         filled circles are used to represent each point. This kind of plot is
@@ -2176,7 +1926,8 @@ def scatter(self, x, y, s=None, c=None, **kwds):
 
         Returns
         -------
-        :class:`matplotlib.axes.Axes` or numpy.ndarray of them
+        axes : :class:`matplotlib.axes.Axes`
+            Return an custom object when ``backend!=matplotlib``.
 
         See Also
         --------
@@ -2209,3 +1960,6 @@ def scatter(self, x, y, s=None, c=None, **kwds):
             ...                       colormap='viridis')
         """
         return self(kind="scatter", x=x, y=y, s=s, c=c, **kwds)
+
+    def hexbin(self, **kwds):
+        return unsupported_function(class_name="pd.DataFrame", method_name="hexbin")()
diff --git a/databricks/koalas/series.py b/databricks/koalas/series.py
--- a/databricks/koalas/series.py
+++ b/databricks/koalas/series.py
@@ -61,7 +61,7 @@
     SPARK_DEFAULT_SERIES_NAME,
 )
 from databricks.koalas.missing.series import MissingPandasLikeSeries
-from databricks.koalas.plot import KoalasSeriesPlotMethods
+from databricks.koalas.plot import KoalasPlotAccessor
 from databricks.koalas.ml import corr
 from databricks.koalas.utils import (
     combine_frames,
@@ -2601,7 +2601,7 @@ def sample(
     def hist(self, bins=10, **kwds):
         return self.plot.hist(bins, **kwds)
 
-    hist.__doc__ = KoalasSeriesPlotMethods.hist.__doc__
+    hist.__doc__ = KoalasPlotAccessor.hist.__doc__
 
     def apply(self, func, args=(), **kwds):
         """
@@ -5063,7 +5063,7 @@ def negative_check(s):
     # ----------------------------------------------------------------------
     dt = CachedAccessor("dt", DatetimeMethods)
     str = CachedAccessor("str", StringMethods)
-    plot = CachedAccessor("plot", KoalasSeriesPlotMethods)
+    plot = CachedAccessor("plot", KoalasPlotAccessor)
 
     # ----------------------------------------------------------------------
 


Test patch: