code: def _bulk_ndcg(recs, truth, discount=np.log2, k=None):
      if "rating" not in truth.columns:
          truth = truth.assign(
              rating=np.ones(len(truth), dtype=np.float32)
          )
  
      ideal = truth.groupby(level="LKTruthID")["rating"].rank(
          method="first", ascending=False
      )
      if k is not None:
          ideal = ideal[ideal <= k]
      ideal = discount(ideal)
      ideal = np.maximum(ideal, 1)
      ideal = truth["rating"] / ideal
      ideal = ideal.groupby(level="LKTruthID").sum()
      ideal.name = "ideal"
  
      list_ideal = recs[["LKRecID", "LKTruthID"]].drop_duplicates()
      list_ideal = list_ideal.join(ideal, on="LKTruthID", how="left")
      list_ideal = list_ideal.set_index("LKRecID")
  
      if k is not None:
          recs = recs[recs["rank"] <= k]
      rated = recs.join(truth, on=["LKTruthID", "item"], how="inner")
      rd = discount(rated["rank"])
      rd = np.maximum(rd, 1)
      rd = rated["rating"] / rd
      rd = rated[["LKRecID"]].assign(util=rd)
      dcg = (
          rd.groupby(["LKRecID"])["util"].sum().reset_index(name="dcg")
      )
      dcg = dcg.set_index("LKRecID")
  
      dcg = dcg.join(list_ideal, how="outer")
      dcg["ndcg"] = dcg["dcg"].fillna(0) / dcg["ideal"]
  
      return dcg["ndcg"]
  
file_path: lkpy/lenskit/metrics/topn.py
imports: import logging
  import warnings
  import numpy
  import pandas
label_desc: Use image node_id 14 for calling a global function with example usage: _bulk_ndcg(recs, truth, discount, k) and returns: dcg