Python 为什么子类化数据帧会改变原始对象?
我忽略了数据帧,并尝试将其子类化。我这样做的理由如下:Python 为什么子类化数据帧会改变原始对象?,python,python-3.x,pandas,dataframe,subclass,Python,Python 3.x,Pandas,Dataframe,Subclass,我忽略了数据帧,并尝试将其子类化。我这样做的理由如下: 我想保留DataFrame的所有现有方法 我想在类实例化时设置一些附加属性,这些属性稍后将用于定义我可以调用子类的附加方法 下面是一个片段: class SubFrame(pd.DataFrame): def __init__(self, *args, **kwargs): freq = kwargs.pop('freq', None) ddof = kwargs.pop('ddof', Non
- 我想保留
的所有现有方法DataFrame
- 我想在类实例化时设置一些附加属性,这些属性稍后将用于定义我可以调用子类的附加方法
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
下面是一个使用示例。假设我有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
其中索引没有频率
print(df.index)
DatetimeIndex(['2014-07-31', '2014-08-31', '2014-09-30', '2014-10-31',
'2014-11-30'],
dtype='datetime64[ns]', freq=None)
使用子帧
允许我在一个步骤中指定该频率:
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')
问题是,这修改了df:
print(df.index.freq)
<MonthEnd>
打印(df.index.freq)
这是怎么回事,我怎么才能避免呢
此外,我还声称使用了一些我不太理解的代码。上面的
\uuuu init\uuuu
中发生了什么?是否需要在此处将args/kwargs与pop
一起使用?(为什么我不能像往常一样指定参数?我将添加到警告中。我并不是想让你气馁,而是为你的努力鼓掌
然而,这并不是你关于发生了什么的最后一个问题
也就是说,一旦你跑步:
super(SubFrame, self).__init__(*args, **kwargs)
self
是一个骨骼fide数据帧。您通过向构造函数传递另一个数据帧来创建它
试着做个实验
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
因此,观察到的行为是一致的,当您通过将另一个数据帧传递给构造函数来构造一个数据帧时,您最终指向相同的对象
为了回答您的问题,子类化并不是允许原始对象发生变异的原因。。。这是熊猫从传递的数据帧构造数据帧的方式
通过使用副本实例化来避免这种情况
d2 = pd.DataFrame(d1.copy())
\uuuuu init\uuuuuu
您希望将所有的args
和kwargs
传递到pd.DataFrame.\uuuu init\uuu
,但用于子类的特定kwargs
除外。在这种情况下,freq
和ddof
pop
是一种方便的方法,可以在将键传递到pd.DataFrame之前从kwargs
中获取值并删除键
如何实现管道
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')
除了“通过使用副本实例化来避免这一点”之外,我一直在关注您。copy()
在super(子帧,self)中应该放在哪里?
为什么?我会根据super
的工作原理怀疑self.copy
,但这是一个抛出的错误,我不确定我是否会制作子帧。因为你会改变熊猫做的事情。相反,我会将您创建变量的方式改为以下方式:sf=SubFrame(df,freq='M')
,sf=SubFrame(df.copy(),freq='M'))
好的——建议使用子类化的方法有两种——您认为这两种方法中的任何一种都适用于我在这个特定案例中尝试做的事情吗?我不确定什么是组合,因为我没有研究过很多CS概念。但我相信我所做的就是写作。我创建了一个新的对象类,其中dataframe是一个属性。其他事情我都是这样处理的。我相信pipe
能帮你解决问题。。。我会用一个例子来更新我的帖子。啊,好的。那么,组合就是我现在所拥有的(在我尝试子类化之前)。最后可能会使用pipe
,请注意,子类化的典型用例是以某种方式修改/扩展基类功能。你不是真的这么做的。您只需以特定的方式设置数据帧。您可能不想创建子类,而只想创建一个工厂类型的函数/对象,该函数/对象只返回以您想要的方式构造的数据帧。子类化对于这个特定的用例来说似乎没有多大意义。“创建一个工厂类型的函数/对象,它只返回一个以您想要的方式构造的数据帧。”您能详细说明一下吗?现在我拥有的是一个标准的类(对象)
,其中dataframe是一个属性。是的,我通过定义更多的其他方法来扩展功能,这里没有显示。注意,他不是子类化的。他正在创建一个函数,该函数以您希望的方式返回数据帧。没有必要子类化。你没有改变行为。factory类的用途之一是创建按您所需方式设置的对象。如果您想在第一个数据帧的副本上创建一个新的数据帧,您可以创建一个函数,该函数将现有数据帧作为参数,复制它,添加频率,然后返回副本。没有子类。