docs: def build_kernel_to_data(self, Y, knn=None, bandwidth=None,
                               bandwidth_scale=None):
          """Build a kernel from new input data `Y` to the `self.data`
  
          Parameters
          ----------
  
          Y: array-like, [n_samples_y, n_features]
              new data for which an affinity matrix is calculated
              to the existing data. `n_features` must match
              either the ambient or PCA dimensions
  
          knn : `int` or `None`, optional (default: `None`)
              If `None`, defaults to `self.knn`
  
          bandwidth : `float`, `callable`, or `None`, optional (default: `None`)
              If `None`, defaults to `self.bandwidth`
  
          bandwidth_scale : `float`, optional (default : `None`)
              Rescaling factor for bandwidth.
              If `None`, defaults to self.bandwidth_scale
  
          Returns
          -------
  
          K_yx: array-like, [n_samples_y, n_samples]
              kernel matrix where each row represents affinities of a single
              sample in `Y` to all samples in `self.data`.
  
          Raises
          ------
  
          ValueError: if the supplied data is the wrong shape
          """
          if knn is None:
              knn = self.knn
          if bandwidth is None:
              bandwidth = self.bandwidth
          if bandwidth_scale is None:
              bandwidth_scale = self.bandwidth_scale
          if knn > self.data.shape[0]:
              warnings.warn("Cannot set knn ({k}) to be greater than "
                            "n_samples ({n}). Setting knn={n}".format(
                                k=knn, n=self.data.shape[0]))
  
          Y = self._check_extension_shape(Y)
          tasklogger.log_start("KNN search")
          if self.decay is None or self.thresh == 1:
              # binary connectivity matrix
              K = self.knn_tree.kneighbors_graph(
                  Y, n_neighbors=knn,
                  mode='connectivity')
              tasklogger.log_complete("KNN search")
          else:
              # sparse fast alpha decay
              knn_tree = self.knn_tree
              search_knn = min(knn * 20, self.data_nu.shape[0])
              distances, indices = knn_tree.kneighbors(
                  Y, n_neighbors=search_knn)
              self._check_duplicates(distances, indices)
              tasklogger.log_complete("KNN search")
              tasklogger.log_start("affinities")
              if bandwidth is None:
                  bandwidth = distances[:, knn - 1]
  
              bandwidth = bandwidth * bandwidth_scale
  
              radius = bandwidth * np.power(-1 * np.log(self.thresh),
                                            1 / self.decay)
              update_idx = np.argwhere(
                  np.max(distances, axis=1) < radius).reshape(-1)
              tasklogger.log_debug("search_knn = {}; {} remaining".format(
                  search_knn, len(update_idx)))
              if len(update_idx) > 0:
                  distances = [d for d in distances]
                  indices = [i for i in indices]
              while len(update_idx) > Y.shape[0] // 10 and \
                      search_knn < self.data_nu.shape[0] / 2:
                  # increase the knn search
                  search_knn = min(search_knn * 20, self.data_nu.shape[0])
                  dist_new, ind_new = knn_tree.kneighbors(
                      Y[update_idx], n_neighbors=search_knn)
                  for i, idx in enumerate(update_idx):
                      distances[idx] = dist_new[i]
                      indices[idx] = ind_new[i]
                  update_idx = [i for i, d in enumerate(distances) if np.max(d) <
                                (radius if isinstance(bandwidth, numbers.Number)
                                 else radius[i])]
                  tasklogger.log_debug("search_knn = {}; {} remaining".format(
                      search_knn,
                      len(update_idx)))
              if search_knn > self.data_nu.shape[0] / 2:
                  knn_tree = NearestNeighbors(
                      search_knn, algorithm='brute',
                      n_jobs=self.n_jobs).fit(self.data_nu)
              if len(update_idx) > 0:
                  tasklogger.log_debug(
                      "radius search on {}".format(len(update_idx)))
                  # give up - radius search
                  dist_new, ind_new = knn_tree.radius_neighbors(
                      Y[update_idx, :],
                      radius=radius
                      if isinstance(bandwidth, numbers.Number)
                      else np.max(radius[update_idx]))
                  for i, idx in enumerate(update_idx):
                      distances[idx] = dist_new[i]
                      indices[idx] = ind_new[i]
              if isinstance(bandwidth, numbers.Number):
                  data = np.concatenate(distances) / bandwidth
              else:
                  data = np.concatenate([distances[i] / bandwidth[i]
                                         for i in range(len(distances))])
  
              indices = np.concatenate(indices)
              indptr = np.concatenate(
                  [[0], np.cumsum([len(d) for d in distances])])
              K = sparse.csr_matrix((data, indices, indptr),
                                    shape=(Y.shape[0], self.data_nu.shape[0]))
              K.data = np.exp(-1 * np.power(K.data, self.decay))
              # handle nan
              K.data = np.where(np.isnan(K.data), 1, K.data)
              # TODO: should we zero values that are below thresh?
              K.data[K.data < self.thresh] = 0
              K = K.tocoo()
              K.eliminate_zeros()
              K = K.tocsr()
              tasklogger.log_complete("affinities")
          return K