2017-07-21 2 views
2

Je suis ignorant le warnings et essayant de sous-classer un DataFrame pandas. Mes raisons pour le faire sont les suivantes:Pourquoi le sous-classement d'un objet DataFrame fait-il muter l'objet d'origine?

  • Je veux conserver toutes les méthodes existantes de DataFrame.
  • Je veux définir quelques attributs supplémentaires à l'instanciation de classe, qui sera plus tard utilisée pour définir des méthodes supplémentaires que je peux appeler sur la sous-classe.

Voici un extrait:

class SubFrame(pd.DataFrame): 

    def __init__(self, *args, **kwargs): 
     freq = kwargs.pop('freq', None) 
     ddof = kwargs.pop('ddof', None) 
     super(SubFrame, self).__init__(*args, **kwargs) 
     self.freq = freq 
     self.ddof = ddof 
     self.index.freq = pd.tseries.frequencies.to_offset(self.freq) 

    @property 
    def _constructor(self): 
     return SubFrame 

Voici un exemple d'utilisation. Dire que j'ai le DataFrame

print(df) 
       col0  col1  col2 
2014-07-31 0.28393 1.84587 -1.37899 
2014-08-31 5.71914 2.19755 3.97959 
2014-09-30 -3.16015 -7.47063 -1.40869 
2014-10-31 5.08850 1.14998 2.43273 
2014-11-30 1.89474 -1.08953 2.67830 

où l'indice n'a pas de fréquence

print(df.index) 
DatetimeIndex(['2014-07-31', '2014-08-31', '2014-09-30', '2014-10-31', 
       '2014-11-30'], 
       dtype='datetime64[ns]', freq=None) 

En utilisant SubFrame me permet de spécifier que la fréquence en une seule étape:

sf = SubFrame(df, freq='M') 
print(sf.index) 
DatetimeIndex(['2014-07-31', '2014-08-31', '2014-09-30', '2014-10-31', 
       '2014-11-30'], 
       dtype='datetime64[ns]', freq='M') 

La question est, ce modifie df:

print(df.index.freq) 
<MonthEnd> 

Que se passe-t-il ici, et comment puis-je éviter cela?

En outre, je prétends utiliser le code copied que je ne comprends pas très bien. Que se passe-t-il dans __init__ ci-dessus? Est-il nécessaire d'utiliser args/kwargs avec pop ici? (Pourquoi ne puis-je pas simplement spécifier les paramètres comme d'habitude?)

+0

Le cas d'utilisation typique pour subclassing est de modifier/étendre la fonctionnalité de la classe de base d'une manière ou d'une autre. Tu ne fais pas vraiment ça ici. Vous configurez simplement votre DataFrame d'une manière spécifique. Plutôt que de sous-classer, vous pouvez simplement créer une fonction/un objet de type usine qui renvoie simplement un DataFrame construit comme vous le souhaitez. Le sous-classement ne semble pas avoir beaucoup de sens pour ce cas particulier. – clockwatcher

+0

"créer une fonction/objet de type usine qui retourne simplement un DataFrame construit comme vous le souhaitez." Pouvez-vous élaborer sur cela s'il vous plaît? En ce moment, j'ai un 'Class (object)' standard où la dataframe est un attribut. Et oui, j'étends la fonctionnalité en définissant plus d'une poignée d'autres méthodes, non montrées ici. –

+1

Regardez la deuxième suggestion de piRSquared avec le tuyau. Notez qu'il ne sous-classe pas. Il crée une fonction qui renvoie simplement un DataFrame comme vous le souhaitez. Il n'y a pas besoin de sous-classe. Vous ne changez pas de comportement. Une des utilisations d'une classe d'usine est de créer l'objet mis en place comme vous le souhaitez. Si vous souhaitez baser un nouveau DataFrame sur une copie de la première, vous devez créer une fonction qui prend en paramètre le DataFrame existant, le copie, ajoute votre fréquence et renvoie la copie. Aucune sous-classe. – clockwatcher

Répondre

3

Je vais ajouter aux avertissements. Non que je veuille te décourager, j'applaudis en fait tes efforts.

Cependant, ce ne sera pas la dernière de vos questions quant à ce qui se passe.

Cela dit, une fois que vous exécutez:

super(SubFrame, self).__init__(*args, **kwargs) 

self est un dataframe os fide. Vous l'avez créé en transmettant une autre image au constructeur.

Essayez ceci comme une expérience

d1 = pd.DataFrame(1, list('AB'), list('XY')) 
d2 = pd.DataFrame(d1) 

d2.index.name = 'IDX' 

d1 

    X Y 
IDX  
A 1 1 
B 1 1 

Ainsi, le comportement observé est cohérent, en ce que lorsque vous construisez une trame de données en passant une autre dataframe au constructeur, vous finissez pointant vers les mêmes objets.

Pour répondre à votre question, le sous-classement n'est pas ce qui permet la mutation de l'objet original ... c'est la façon dont pandas construit une trame de données à partir d'une trame de données passée.

Pour éviter cela instancier une copie

d2 = pd.DataFrame(d1.copy()) 

Qu'est-ce qui se passe dans le __init__

Vous voulez transmettre toutes les args et kwargs-pd.DataFrame.__init__ à l'exception de la kwargs spécifique qui sont destinés à votre sous-classe. Dans ce cas, freq et ddof. pop est un moyen pratique de saisir les valeurs et supprimer la clé de kwargs avant de le transmettre à pd.DataFrame.__init__


Comment je

mettre en œuvre pipe
def add_freq(df, freq): 
    df = df.copy() 
    df.index.freq = pd.tseries.frequencies.to_offset(freq) 
    return df 

df = pd.DataFrame(dict(A=[1, 2]), pd.to_datetime(['2017-03-31', '2017-04-30'])) 

df.pipe(add_freq, 'M') 
+0

Je vous suis à l'exception de "Évitez cela en instanciant avec une copie." Où 'copy()' irait-il dans 'super (SubFrame, self) ...' et pourquoi? J'aurais suspecté 'self.copy' basé sur comment' super' fonctionne, mais c'est une erreur de lancement –

+0

Je ne suis pas sûr que je ferais une copie de 'SubFrame .__ init__' (lisez cela car je n'en suis pas vraiment sûr). Je pourrais balancer de toute façon). Parce que vous changeriez quelque chose que les pandas font. Au lieu de cela, je changerais la façon dont vous avez créé votre variable 'sf':' sf = SubFrame (df, freq = 'M') 'à ceci à la place' sf = SubFrame (df.copy(), freq = 'M') ' – piRSquared

+0

Ok - et il y a 2 alternatives à la sous-classification suggérées [ici] (http://pandas.pydata.org/pandas-docs/stable/internals.html#subclassing-pandas-data-structures) - diriez-vous que l'un ou l'autre s'applique-t-il à ce que j'essaie de faire dans ce cas précis? –