class_docstr: Compute one or more top-N metrics over recommendation lists.
  
  This method groups the recommendations by the specified columns,
  and computes the metric over each group.  The default set of grouping
  columns is all columns *except* the following:
  
  * ``item``
  * ``rank``
  * ``score``
  * ``rating``
  
  The truth frame, ``truth``, is expected to match over (a subset of) the
  grouping columns, and contain at least an ``item`` column.  If it also
  contains a ``rating`` column, that is used as the users' rating for
  metrics that require it; otherwise, a rating value of 1 is assumed.
  
  Args:
      group_cols(list):
          The columns to group by, or ``None`` to use the default.
docstr: Run the analysis.  Neither data frame should be meaningfully indexed.
  
  Args:
      recs(pandas.DataFrame):
          A data frame of recommendations.
      truth(pandas.DataFrame):
          A data frame of ground truth (test) data.
      include_missing(bool):
          ``True`` to include users from truth missing from recs.
          Matches are done via group columns that appear in both
          ``recs`` and ``truth``.
  
  Returns:
      pandas.DataFrame: The results of the analysis.
docstr_tok: ["Run","the","analysis",".","Neither","data","frame","should","be","meaningfully","indexed",".","Args",":","recs","(","pandas.DataFrame",")",":","A","data","frame","of","recommendations",".","truth","(","pandas.DataFrame",")",":","A","data","frame","of","ground","truth","(","test",")","data",".","include_missing","(","bool",")",":","``","True","``","to","include","users","from","truth","missing","from","recs",".","Matches","are","done","via","group","columns","that","appear","in","both","``","recs","``","and","``","truth","``",".","Returns",":","pandas.DataFrame",":","The","results","of","the","analysis","."]
code: def compute(self, recs, truth, *, include_missing=False):
      """
      Run the analysis.  Neither data frame should be meaningfully indexed.
  
      Args:
          recs(pandas.DataFrame):
              A data frame of recommendations.
          truth(pandas.DataFrame):
              A data frame of ground truth (test) data.
          include_missing(bool):
              ``True`` to include users from truth missing from recs.
              Matches are done via group columns that appear in both
              ``recs`` and ``truth``.
  
      Returns:
          pandas.DataFrame: The results of the analysis.
      """
      _log.info(
          "analyzing %d recommendations (%d truth rows)",
          len(recs),
          len(truth),
      )
  
      rec_key, truth_key = _df_keys(
          recs.columns, truth.columns, self.group_cols
      )
  
      t_ident, t_data = self._number_truth(truth, truth_key)
      r_ident, r_data = self._number_recs(
          recs, truth_key, rec_key, t_ident
      )
  
      timer = Stopwatch()
  
      _log.info("collecting metric results")
      bulk_res = []
      ind_metrics = []
      for mf, mn, margs in self.metrics:
          if hasattr(mf, "bulk_score") and "rank" in r_data.columns:
              _log.debug("bulk-scoring %s", mn)
              mbs = mf.bulk_score(r_data, t_data, **margs).to_frame(
                  name=mn
              )
              assert mbs.index.name == "LKRecID"
              bulk_res.append(mbs)
          else:
              ind_metrics.append((mf, mn, margs))
      if bulk_res:
          bulk_res = ft.reduce(
              lambda df1, df2: df1.join(df2, how="outer"), bulk_res
          )
      else:
          bulk_res = None
  
      def worker(rdf):
          rk, tk = rdf.name
          tdf = t_data.loc[tk]
          res = pd.Series(
              dict(
                  (mn, mf(rdf, tdf, **margs))
                  for (mf, mn, margs) in ind_metrics
              )
          )
          return res
  
      if ind_metrics:
          _log.debug("applying individual metrics")
          groups = r_data.groupby(["LKRecID", "LKTruthID"], sort=False)
          if hasattr(groups, "progress_apply"):
              ind_res = groups.progress_apply(worker)
          else:
              ind_res = groups.apply(worker)
          ind_res = ind_res.reset_index("LKTruthID", drop=True)
  
          if bulk_res is not None:
              res = bulk_res.join(ind_res)
          else:
              res = ind_res
      else:
          res = bulk_res
  
      _log.debug("transforming results")
      res = r_ident.join(res, on="LKRecID").drop(
          columns=["LKRecID", "LKTruthID"]
      )
  
      _log.info("measured %d lists in %s", len(res), timer)
  
      if include_missing:
          _log.info(
              "filling in missing user info (%d initial rows)", len(res)
          )
          ug_cols = [c for c in rec_key if c not in truth_key]
          tcount = (
              truth.groupby(truth_key)["item"]
              .count()
              .to_frame("ntruth")
          )
          _log.debug("truth counts:\n%s", tcount)
          if ug_cols:
              _log.debug("regrouping by %s to fill", ug_cols)
              _log.debug("pre-group series:\n%s", res)
  
              rdict = {}
  
              for key, df in res.groupby(ug_cols):
                  df2 = df.drop(columns=ug_cols).join(
                      tcount, how="outer", on=truth_key
                  )
                  rdict[key] = df2
  
              res = pd.concat(rdict, names=ug_cols)
              _log.debug("joined result:\n%s", res)
              res = res.reset_index(ug_cols)
              res.reset_index(inplace=True, drop=True)
              _log.debug("final joined result:\n%s", res)
  
          else:
              _log.debug("no ungroup cols, directly merging to fill")
              res = res.join(tcount, how="outer", on=truth_key)
          _log.debug("final columns: %s", res.columns)
          _log.debug("index levels: %s", res.index.names)
          _log.debug("expanded to %d rows", len(res))
          res["ntruth"] = res["ntruth"].fillna(0)
          res["nrecs"] = res["nrecs"].fillna(0)
  
      return res.set_index(rec_key)
  
file_path: lkpy/lenskit/topn.py
imports: import logging
  import functools
  import numpy
  import pandas
  from .metrics.topn import *
  from .util import Stopwatch
label_desc: Use image node_id 3 for calling the RecListAnalysis obj's underlying member method code with example usage: obj.compute(recs, truth) and returns: res, res