class_docstr: Empirical precision matrix.
  
  The precision matrix is the inverse of the covariance matrix.
  
  This implementation leverages the Sherman-Morrison formula. The resulting inverse covariance
  matrix is not guaranteed to be identical to a batch computation. However, the difference
  shrinks with the number of observations.
  
  Examples
  --------
  
  >>> import numpy as np
  >>> import pandas as pd
  >>> from river import covariance
  
  >>> np.random.seed(42)
  >>> X = pd.DataFrame(np.random.random((1000, 3)))
  >>> X.head()
            0         1         2
  0  0.374540  0.950714  0.731994
  1  0.598658  0.156019  0.155995
  2  0.058084  0.866176  0.601115
  3  0.708073  0.020584  0.969910
  4  0.832443  0.212339  0.181825
  
  >>> prec = covariance.EmpiricalPrecision()
  >>> for x in X.to_dict(orient="records"):
  ...     prec.update(x)
  
  >>> prec
      0        1        2
  0   12.026   -0.122   -0.214
  1   -0.122   11.276   -0.026
  2   -0.214   -0.026   11.632
  
  >>> pd.DataFrame(np.linalg.inv(np.cov(X.T, ddof=1)))
             0          1          2
  0  12.159791  -0.124966  -0.218671
  1  -0.124966  11.393394  -0.026662
  2  -0.218671  -0.026662  11.756907
  
  References
  ----------
  [^1]: [Online Estimation of the Inverse Covariance Matrix - Markus Thill](https://markusthill.github.io/math/stats/ml/online-estimation-of-the-inverse-covariance-matrix/)
  [^2]: [Fast rank-one updates to matrix inverse? - Tim Vieira](https://timvieira.github.io/blog/post/2021/03/25/fast-rank-one-updates-to-matrix-inverse/)
  [^3]: [Woodbury matrix identity](https://www.wikiwand.com/en/Woodbury_matrix_identity)
docstr: Update with a dataframe of samples.
  
  Parameters
  ----------
  X
      A dataframe of samples.
docstr_tok: ["Update","with","a","dataframe","of","samples",".","Parameters","--","--","--","--","--","X","A","dataframe","of","samples","."]
code: def update_many(self, X: pd.DataFrame):
      """Update with a dataframe of samples.
  
      Parameters
      ----------
      X
          A dataframe of samples.
  
      """
  
      # numpy -> dict
      X_arr = X.values
      loc = np.array([self._loc.get(feature, 0.0) for feature in X])
      w = np.array([self._w.get(feature, 0.0) for feature in X])
      inv_cov = np.array(
          [
              [
                  self._inv_cov.get(
                      min((i, j), (j, i)), 1.0 if i == j else 0.0
                  )
                  for j in X
              ]
              for i in X
          ]
      ) / np.maximum(w, 1)
  
      # update formulas
      diff = X_arr - loc
      loc = (w * loc + len(X) * X_arr.mean(axis=0)) / (w + len(X))
      w += len(X)
      utils.math.woodbury_matrix(A=inv_cov, U=diff.T, V=X_arr - loc)
  
      # numpy -> dict
      for i, fi in enumerate(X):
          self._loc[fi] = loc[i]
          self._w[fi] = w[i]
          row = self._w[fi] * inv_cov[i]
          for j, fj in enumerate(X):
              self._inv_cov[min((fi, fj), (fj, fi))] = row[j]
  
      return self
  
imports: from __future__ import annotations
  import abc
  import itertools
  import numpy
  import pandas
  from river import stats, utils
label_desc: Use image node_id 4 for calling the EmpiricalPrecision obj's underlying member method code with example usage: obj.update_many(X) and returns: self