code: def fit(self, data, d=None, tol=1e-4, min_obs=10, verbose=False):
      self.raw = data
      self.raw[np.isinf(self.raw)] = np.max(
          self.raw[np.isfinite(self.raw)]
      )
  
      valid_series = np.sum(~np.isnan(self.raw), axis=0) >= min_obs
  
      data = self.raw[:, valid_series].copy()
      N = data.shape[0]
      D = data.shape[1]
  
      self.means = np.nanmean(data, axis=0)
      self.stds = np.nanstd(data, axis=0)
  
      data = self._standardize(data)
      observed = ~np.isnan(data)
      missing = np.sum(~observed)
      data[~observed] = 0
  
      # initial
  
      if d is None:
          d = data.shape[1]
  
      if self.C is None:
          C = np.random.randn(D, d)
      else:
          C = self.C
      CC = np.dot(C.T, C)
      X = np.dot(np.dot(data, C), np.linalg.inv(CC))
      recon = np.dot(X, C.T)
      recon[~observed] = 0
      ss = np.sum((recon - data) ** 2) / (N * D - missing)
  
      v0 = np.inf
      counter = 0
  
      while True:
          Sx = np.linalg.inv(np.eye(d) + CC / ss)
  
          # e-step
          ss0 = ss
          if missing > 0:
              proj = np.dot(X, C.T)
              data[~observed] = proj[~observed]
          X = np.dot(np.dot(data, C), Sx) / ss
  
          # m-step
          XX = np.dot(X.T, X)
          C = np.dot(np.dot(data.T, X), np.linalg.pinv(XX + N * Sx))
          CC = np.dot(C.T, C)
          recon = np.dot(X, C.T)
          recon[~observed] = 0
          ss = (
              np.sum((recon - data) ** 2)
              + N * np.sum(CC * Sx)
              + missing * ss0
          ) / (N * D)
  
          # calc diff for convergence
          det = np.log(np.linalg.det(Sx))
          if np.isinf(det):
              det = abs(np.linalg.slogdet(Sx)[1])
          v1 = (
              N * (D * np.log(ss) + np.trace(Sx) - det)
              + np.trace(XX)
              - missing * np.log(ss0)
          )
          diff = abs(v1 / v0 - 1)
          if verbose:
              print(diff)
          if (diff < tol) and (counter > 5):
              break
  
          counter += 1
          v0 = v1
  
      C = orth(C)
      vals, vecs = np.linalg.eig(np.cov(np.dot(data, C).T))
      order = np.flipud(np.argsort(vals))
      vecs = vecs[:, order]
      vals = vals[order]
  
      C = np.dot(C, vecs)
  
      # attach objects to class
      self.C = C
      self.data = data
      self.eig_vals = vals
      self._calc_var()
  
file_path: hypertools/hypertools/_externals/ppca.py
imports: import os
  import numpy
  from scipy.linalg import orth
label_desc: Use image node_id 3 for calling the PPCA obj's underlying member method code with example usage: obj.fit(data, d, tol, min_obs, verbose) without return types