code: def scatter_ellipse(
      data,
      level=0.9,
      varnames=None,
      ell_kwds=None,
      plot_kwds=None,
      add_titles=False,
      keep_ticks=False,
      fig=None,
  ):
      """Create a grid of scatter plots with confidence ellipses.
  
      ell_kwds, plot_kdes not used yet
  
      looks ok with 5 or 6 variables, too crowded with 8, too empty with 1
  
      Parameters
      ----------
      data : array_like
          Input data.
      level : scalar, optional
          Default is 0.9.
      varnames : list[str], optional
          Variable names.  Used for y-axis labels, and if `add_titles` is True
          also for titles.  If not given, integers 1..data.shape[1] are used.
      ell_kwds : dict, optional
          UNUSED
      plot_kwds : dict, optional
          UNUSED
      add_titles : bool, optional
          Whether or not to add titles to each subplot.  Default is False.
          Titles are constructed from `varnames`.
      keep_ticks : bool, optional
          If False (default), remove all axis ticks.
      fig : Figure, optional
          If given, this figure is simply returned.  Otherwise a new figure is
          created.
  
      Returns
      -------
      Figure
          If `fig` is None, the created figure.  Otherwise `fig` itself.
  
      Examples
      --------
      >>> import statsmodels.api as sm
      >>> import matplotlib.pyplot as plt
      >>> import numpy as np
  
      >>> from statsmodels.graphics.plot_grids import scatter_ellipse
      >>> data = sm.datasets.statecrime.load_pandas().data
      >>> fig = plt.figure(figsize=(8,8))
      >>> scatter_ellipse(data, varnames=data.columns, fig=fig)
      >>> plt.show()
  
      .. plot:: plots/graphics_plot_grids_scatter_ellipse.py
      """
      fig = utils.create_mpl_fig(fig)
      import matplotlib.ticker as mticker
  
      data = np.asanyarray(data)  # needs mean and cov
      nvars = data.shape[1]
      if varnames is None:
          # assuming single digit, nvars<=10  else use 'var%2d'
          varnames = ["var%d" % i for i in range(nvars)]
  
      plot_kwds_ = dict(ls="none", marker=".", color="k", alpha=0.5)
      if plot_kwds:
          plot_kwds_.update(plot_kwds)
  
      ell_kwds_ = dict(color="k")
      if ell_kwds:
          ell_kwds_.update(ell_kwds)
  
      dmean = data.mean(0)
      dcov = np.cov(data, rowvar=0)
  
      for i in range(1, nvars):
          # print '---'
          ax_last = None
          for j in range(i):
              # print i,j, i*(nvars-1)+j+1
              ax = fig.add_subplot(
                  nvars - 1, nvars - 1, (i - 1) * (nvars - 1) + j + 1
              )
              ##                                 #sharey=ax_last) #sharey does not allow empty ticks?
              ##            if j == 0:
              ##                print 'new ax_last', j
              ##                ax_last = ax
              ##                ax.set_ylabel(varnames[i])
              # TODO: make sure we have same xlim and ylim
  
              formatter = mticker.FormatStrFormatter("% 3.1f")
              ax.yaxis.set_major_formatter(formatter)
              ax.xaxis.set_major_formatter(formatter)
  
              idx = np.array([j, i])
              ax.plot(*data[:, idx].T, **plot_kwds_)
  
              if np.isscalar(level):
                  level = [level]
              for alpha in level:
                  _make_ellipse(
                      dmean[idx],
                      dcov[idx[:, None], idx],
                      ax,
                      level=alpha,
                      **ell_kwds_,
                  )
  
              if add_titles:
                  ax.set_title("{}-{}".format(varnames[i], varnames[j]))
              if not ax.is_first_col():
                  if not keep_ticks:
                      ax.set_yticks([])
                  else:
                      ax.yaxis.set_major_locator(mticker.MaxNLocator(3))
              else:
                  ax.set_ylabel(varnames[i])
              if ax.is_last_row():
                  ax.set_xlabel(varnames[j])
              else:
                  if not keep_ticks:
                      ax.set_xticks([])
                  else:
                      ax.xaxis.set_major_locator(mticker.MaxNLocator(3))
  
              dcorr = np.corrcoef(data, rowvar=0)
              dc = dcorr[idx[:, None], idx]
              xlim = ax.get_xlim()
              ylim = ax.get_ylim()
              ##            xt = xlim[0] + 0.1 * (xlim[1] - xlim[0])
              ##            yt = ylim[0] + 0.1 * (ylim[1] - ylim[0])
              ##            if dc[1,0] < 0 :
              ##                yt = ylim[0] + 0.1 * (ylim[1] - ylim[0])
              ##            else:
              ##                yt = ylim[1] - 0.2 * (ylim[1] - ylim[0])
              yrangeq = ylim[0] + 0.4 * (ylim[1] - ylim[0])
              if dc[1, 0] < -0.25 or (
                  dc[1, 0] < 0.25 and dmean[idx][1] > yrangeq
              ):
                  yt = ylim[0] + 0.1 * (ylim[1] - ylim[0])
              else:
                  yt = ylim[1] - 0.2 * (ylim[1] - ylim[0])
              xt = xlim[0] + 0.1 * (xlim[1] - xlim[0])
              ax.text(xt, yt, "$\\rho=%0.2f$" % dc[1, 0])
  
      for ax in fig.axes:
          if ax.is_last_row():  # or ax.is_first_col():
              ax.xaxis.set_major_locator(mticker.MaxNLocator(3))
          if ax.is_first_col():
              ax.yaxis.set_major_locator(mticker.MaxNLocator(3))
  
      return fig
  
imports: import numpy
  from scipy import stats
  from .None import utils
label_desc: Use image node_id 2 for calling a global function with example usage: scatter_ellipse(data, level, varnames, ell_kwds, plot_kwds, add_titles, keep_ticks, fig) and returns: fig