docs: def get_lightcurve(data, copy=False, name=None,
                     predictor=None, periodogram=Lomb_Scargle,
                     sigma_clipping=mad,
                     scoring='r2', scoring_cv=3, scoring_processes=1,
                     period=None, min_period=0.2, max_period=32,
                     coarse_precision=1e-5, fine_precision=1e-9,
                     period_processes=1,
                     sigma=20,
                     shift=None,
                     min_phase_cover=0.0, min_observations=1, n_phases=100,
                     verbosity=None, **kwargs):
      """get_lightcurve(data, copy=False, name=None, predictor=None, periodogram=Lomb_Scargle, sigma_clipping=mad, scoring='r2', scoring_cv=3, scoring_processes=1, period=None, min_period=0.2, max_period=32, coarse_precision=1e-5, fine_precision=1e-9, period_processes=1, sigma=20, shift=None, min_phase_cover=0.0, n_phases=100, verbosity=None, **kwargs)
  
      Fits a light curve to the given `data` using the specified methods,
      with default behavior defined for all methods.
  
      **Parameters**
  
      data : array-like, shape = [n_samples, 2] or [n_samples, 3]
          Photometry array with columns *time*, *magnitude*, and (optional)
          *error*. *time* should be unphased.
      name : string or None, optional
          Name of star being processed.
      predictor : object that has "fit" and "predict" methods, optional
          Object which fits the light curve obtained from *data* after rephasing
          (default ``make_predictor(scoring=scoring, scoring_cv=scoring_cv)``).
      periodogram : function, optional
          Function which finds one or more *period*\s. If *period* is already
          provided, the function is not used. Defaults to
          :func:`plotypus.periodogram.Lomb_Scargle`
      sigma_clipping : function, optional
          Function which takes an array and assigns sigma scores to each element.
          Defaults to :func:`plotypus.utils.mad`.
      scoring : str, optional
          Scoring method used by *predictor*. This parameter can be
              * "r2", in which case use :func:`R^2` (the default)
              * "mse", in which case use mean square error
      scoring_cv : positive integer, optional
          Number of cross validation folds used in scoring (default 3).
      scoring_processes : positive integer, optional
          Number of processes to use for scoring cross validation (default 1).
      period : number or None, optional
          Period of oscillation used in the fit. This parameter can be:
              * None, in which case the period is obtained with the given
                *periodogram* function (the default).
              * A single positive number, giving the period to phase *data*.
      min_period : non-negative number, optional
          Lower bound on period obtained by *periodogram* (default 0.2).
      max_period : non-negative number, optional
          Upper bound on period obtained by *periodogram* (default 32.0).
      course_precision : positive number, optional
          Precision used in first period search sweep (default 1e-5).
      fine_precision : positive number, optional
          Precision used in second period search sweep (default 1e-9).
      period_processes : positive integer, optional
          Number of processes to use for period finding (default 1).
      sigma : number, optional
          Upper bound on score obtained by *sigma_clipping* for a point to be
          considered an inlier.
      shift : number or None, optional
          Phase shift to apply to light curve if provided. Light curve is shifted
          such that max light occurs at ``phase[0]`` if None given (default None).
      min_phase_cover : number on interval [0, 1], optional
          Fraction of binned light curve that must contain points in order to
          proceed. If light curve has insufficient coverage, a warning is
          printed if "outlier" *verbosity* is on, and None is returned
          (default 0.0).
      n_phases : positive integer
          Number of equally spaced phases to predict magnitudes at (default 100)
      verbosity : list or None, optional
          Verbosity level. See :func:`plotypus.utils.verbose_print`.
  
      **Returns**
  
      out : dict
          Results of the fit in a dictionary. The keys are:
              * name : str or None
                  The name of the star.
              * period : number
                  The star's period.
              * lightcurve : array-like, shape = [n_phases]
                  Magnitudes of fitted light curve sampled at sample phases.
              * coefficients : array-like, shape = [n_coeffs]
                  Fitted light curve coefficients.
              * dA_0 : non-negative number
                  Error on mean magnitude.
              * phased_data : array-like, shape = [n_samples]
                  *data* transformed from temporal to phase space.
              * model : predictor object
                  The predictor used to fit the light curve.
              * R2 : number
                  The :math:`R^2` score of the fit.
              * MSE : number
                  The mean square error of the fit.
              * degree : positive integer
                  The degree of the Fourier fit.
              * shift : number
                  The phase shift applied.
              * coverage : number on interval [0, 1]
                  The light curve coverage.
  
      **See also**
  
      :func:`get_lightcurve_from_file`
      """
      data = numpy.ma.array(data, copy=copy)
      phases = numpy.linspace(0, 1, n_phases, endpoint=False)
  # TODO ###
  # Replace dA_0 with error matrix dA
      if predictor is None:
          predictor = make_predictor(scoring=scoring, scoring_cv=scoring_cv)
  
      while True:
          signal = get_signal(data)
          if len(signal) <= scoring_cv:
              verbose_print(
                  "{}: length of signal ({}) less than cv folds ({})".format(
                      name, len(signal), scoring_cv),
                  operation="coverage", verbosity=verbosity)
              return
          elif len(signal) < min_observations:
              verbose_print(
                  "{}: length of signal ({}) "
                  "less than min_observations ({})".format(
                      name, len(signal), min_observations),
                  operation="coverage", verbosity=verbosity)
              return
          # Find the period of the inliers
          if period is not None:
              _period = period
          else:
              verbose_print("{}: finding period".format(name),
                            operation="period", verbosity=verbosity)
              _period = find_period(signal,
                                    min_period, max_period,
                                    coarse_precision, fine_precision,
                                    periodogram, period_processes)
  
          verbose_print("{}: using period {}".format(name, _period),
                        operation="period", verbosity=verbosity)
          phase, mag, *err = rephase(signal, _period).T
  
  # TODO ###
  # Generalize number of bins to function parameter ``coverage_bins``, which
  # defaults to 100, the current hard-coded behavior
          # Determine whether there is sufficient phase coverage
          coverage = numpy.zeros((100))
          for p in phase:
              coverage[int(floor(p*100))] = 1
          coverage = sum(coverage)/100
          if coverage < min_phase_cover:
              verbose_print("{}: {} {}".format(name, coverage, min_phase_cover),
                            operation="coverage",
                            verbosity=verbosity)
              verbose_print("Insufficient phase coverage",
                            operation="outlier",
                            verbosity=verbosity)
              return
  
          # Predict light curve
          with warnings.catch_warnings(record=True) as w:
              try:
                  predictor = predictor.fit(colvec(phase), mag)
              except Warning:
                  # not sure if this should be only in verbose mode
                  print(name, w, file=stderr)
                  return
  
          # Reject outliers and repeat the process if there are any
          if sigma:
              outliers = find_outliers(rephase(data.data, _period), predictor,
                                       sigma, sigma_clipping)
              num_outliers = sum(outliers)[0]
              if num_outliers == 0 or \
                 set.issubset(set(numpy.nonzero(outliers.T[0])[0]),
                              set(numpy.nonzero(data.mask.T[0])[0])):
                  data.mask = outliers
                  break
              if num_outliers > 0:
                  verbose_print("{}: {} outliers".format(name, sum(outliers)[0]),
                                operation="outlier",
                                verbosity=verbosity)
              data.mask = numpy.ma.mask_or(data.mask, outliers)
  
      # Build light curve and optionally shift to max light
      lightcurve = predictor.predict([[i] for i in phases])
      if shift is None:
          arg_max_light = lightcurve.argmin()
          lightcurve = numpy.concatenate((lightcurve[arg_max_light:],
                                          lightcurve[:arg_max_light]))
          shift = arg_max_light/len(phases)
  
      data.T[0] = rephase(data.data, _period, shift).T[0]
  
      # Grab the coefficients from the model
      coefficients = predictor.named_steps['Regressor'].coef_ \
          if isinstance(predictor, Pipeline) \
          else predictor.best_estimator_.named_steps['Regressor'].coef_,
  
      # compute R^2 and MSE if they haven't already been
      # (one or zero have been computed, depending on the predictor)
      estimator = predictor.best_estimator_ \
          if hasattr(predictor, 'best_estimator_') \
          else predictor
  
      get_score = lambda scoring: predictor.best_score_ \
          if hasattr(predictor, 'best_score_') \
          and predictor.scoring == scoring \
          else cross_val_score(estimator, colvec(phase), mag,
                               cv=scoring_cv, scoring=scoring,
                               n_jobs=scoring_processes).mean()
  
      return {'name':         name,
              'period':       _period,
              'lightcurve':   lightcurve,
              'coefficients': coefficients[0],
              'dA_0':         sem(lightcurve),
              'phased_data':  data,
              'model':        predictor,
              'R2':           get_score('r2'),
              'MSE':          abs(get_score('mean_squared_error')),
              'degree':       estimator.get_params()['Fourier__degree'],
              'shift':        shift,
              'coverage':     coverage}