title: Observer for pandas dataframe
body: <p>I'm trying to create an observer for a DataFrame. I've created a class that has a dataframe as an instance attribute. In this class I made an observer using the setter decorator, but when I modify or add a single column the setter is not called.</p>
  <p>The class is like this</p>
  <pre><code>class ProvenanceTracker:
      def __init__(self, initial_df):
          self._df = initial_df
          self._copy_df=initial_df.copy()
          self.shape_change = False
          self.value_change = False
      def dataframe_is_changed(self):
          if self._df.shape==self._copy_df.shape:
              if not self._df.equals(self._copy_df):
                  print('difference founded')
                  self.value_change = True
              else:
                  print('same df')
                  pass
          else:
              print('shape changed detected')
              self.shape_change = True
      
      @property
      def df(self):
          return self._df
      
      @df.setter
      def df(self, new_value):
          self._df = new_value
          self.dataframe_is_changed()
          if self.shape_change:
              ##Do something
              self._copy_df=self._df.copy()
              self.shape_change = False
          elif self.value_change:
              ##Do something
              self._copy_df=self._df.copy()
              self.value_change = False
  </code></pre>
  <p>An example:</p>
  <pre><code>df = pd.DataFrame([[1, 2], [3, 4]], columns=list('AB'))
  
  tracker=ProvenanceTracker(df)
  </code></pre>
  <p>if I try to add a new column or modify a column the setter method isn't called</p>
  <pre><code>tracker.df['test']=np.zeros(tracker.df.shape[0])
  </code></pre>
  <p>if I do an operation on all the df the setter is called</p>
  <pre><code>tracker.df = tracker.df.replace(2, 5)
  </code></pre>
  <p>There is a way to call a method everytime the df changes?</p>