Scatter-plot matrix with lowess smoother
Question: **What would the Python code be for a scatter-plot matrix with lowess
smoothers similar to the following one?** ![enter image description
here](http://i.stack.imgur.com/1A6GN.png)

I'm not sure about the original source of the graph. I saw it on [this
post](http://stats.stackexchange.com/questions/64789/exploring-a-scatter-plot-
matrix-for-many-variables) on CrossValidated. The ellipses define the
covariance according to the original post. I'm not sure what the numbers mean.


Answer: I adapted the pandas scatter_matrix function and got a decent result:

    
    
    import pandas as pd
    import numpy as np
    frame = pd.DataFrame(np.random.randn(100, 4), columns=['A','B','C','D'])
    fig = scatter_matrix_lowess(frame, alpha=0.4, figsize=(12,12));
    fig.suptitle('Scatterplot matrix with lowess smoother', fontsize=16);
    

![Scatterplot matrix with lowess smoother](http://i.stack.imgur.com/sti1R.png)

* * *

This is the code for `scatter_matrix_lowess`:

    
    
    def scatter_matrix_lowess(frame, alpha=0.5, figsize=None, grid=False,
                       diagonal='hist', marker='.', density_kwds=None,
                       hist_kwds=None, range_padding=0.05, **kwds):
        """
        Draw a matrix of scatter plots with lowess smoother.
        This is an adapted version of the pandas scatter_matrix function.
    
        Parameters
        ----------
        frame : DataFrame
        alpha : float, optional
            amount of transparency applied
        figsize : (float,float), optional
            a tuple (width, height) in inches
        ax : Matplotlib axis object, optional
        grid : bool, optional
            setting this to True will show the grid
        diagonal : {'hist', 'kde'}
            pick between 'kde' and 'hist' for
            either Kernel Density Estimation or Histogram
            plot in the diagonal
        marker : str, optional
            Matplotlib marker type, default '.'    
        hist_kwds : other plotting keyword arguments
            To be passed to hist function
        density_kwds : other plotting keyword arguments
            To be passed to kernel density estimate plot
        range_padding : float, optional
            relative extension of axis range in x and y
            with respect to (x_max - x_min) or (y_max - y_min),
            default 0.05
        kwds : other plotting keyword arguments
            To be passed to scatter function
    
        Examples
        --------
        >>> df = DataFrame(np.random.randn(1000, 4), columns=['A','B','C','D'])
        >>> scatter_matrix_lowess(df, alpha=0.2)
        """
    
        import matplotlib.pyplot as plt
        from matplotlib.artist import setp
        import pandas.core.common as com
        from pandas.compat import range, lrange, lmap, map, zip
        from statsmodels.nonparametric.smoothers_lowess import lowess
    
        df = frame._get_numeric_data()
        n = df.columns.size
        fig, axes = plt.subplots(nrows=n, ncols=n, figsize=figsize, squeeze=False)
    
        # no gaps between subplots
        fig.subplots_adjust(wspace=0, hspace=0)
    
        mask = com.notnull(df)
    
        marker = _get_marker_compat(marker)
    
        hist_kwds = hist_kwds or {}
        density_kwds = density_kwds or {}
    
        # workaround because `c='b'` is hardcoded in matplotlibs scatter method
        kwds.setdefault('c', plt.rcParams['patch.facecolor'])
    
        boundaries_list = []
        for a in df.columns:
            values = df[a].values[mask[a].values]
            rmin_, rmax_ = np.min(values), np.max(values)
            rdelta_ext = (rmax_ - rmin_) * range_padding / 2.
            boundaries_list.append((rmin_ - rdelta_ext, rmax_+ rdelta_ext))
    
        for i, a in zip(lrange(n), df.columns):
            for j, b in zip(lrange(n), df.columns):
                ax = axes[i, j]
    
                if i == j:
                    values = df[a].values[mask[a].values]
    
                    # Deal with the diagonal by drawing a histogram there.
                    if diagonal == 'hist':
                        ax.hist(values, **hist_kwds)
    
                    elif diagonal in ('kde', 'density'):
                        from scipy.stats import gaussian_kde
                        y = values
                        gkde = gaussian_kde(y)
                        ind = np.linspace(y.min(), y.max(), 1000)
                        ax.plot(ind, gkde.evaluate(ind), **density_kwds)
    
                    ax.set_xlim(boundaries_list[i])
    
                else:
                    common = (mask[a] & mask[b]).values
    
                    ax.scatter(df[b][common], df[a][common],
                               marker=marker, alpha=alpha, **kwds)
                    # The following 2 lines are new and add the lowess smoothing
                    ys = lowess(df[a][common], df[b][common])
                    ax.plot(ys[:,0], ys[:,1], 'red', linewidth=1)
    
                    ax.set_xlim(boundaries_list[j])
                    ax.set_ylim(boundaries_list[i])
    
                ax.set_xlabel('')
                ax.set_ylabel('')
    
                _label_axis(ax, kind='x', label=b, position='bottom', rotate=True)
    
                _label_axis(ax, kind='y', label=a, position='left')
    
                if j!= 0:
                    ax.yaxis.set_visible(False)
                if i != n-1:
                    ax.xaxis.set_visible(False)
    
        for ax in axes.flat:
            setp(ax.get_xticklabels(), fontsize=8)
            setp(ax.get_yticklabels(), fontsize=8)
        return fig
    
    def _label_axis(ax, kind='x', label='', position='top',
        ticks=True, rotate=False):
    
        from matplotlib.artist import setp
        if kind == 'x':
            ax.set_xlabel(label, visible=True)
            ax.xaxis.set_visible(True)
            ax.xaxis.set_ticks_position(position)
            ax.xaxis.set_label_position(position)
            if rotate:
                setp(ax.get_xticklabels(), rotation=90)
        elif kind == 'y':
            ax.yaxis.set_visible(True)
            ax.set_ylabel(label, visible=True)
            # ax.set_ylabel(a)
            ax.yaxis.set_ticks_position(position)
            ax.yaxis.set_label_position(position)
        return
    
    def _get_marker_compat(marker):
        import matplotlib.lines as mlines
        import matplotlib as mpl
        if mpl.__version__ < '1.1.0' and marker == '.':
            return 'o'
        if marker not in mlines.lineMarkers:
            return 'o'
        return marker
    

