为什么子类化 DataFrame 会改变原始对象?
Why does subclassing a DataFrame mutate the original object?
我正在忽略 warnings 并尝试子 class 一个 pandas DataFrame。我这样做的原因如下:
- 我想保留
DataFrame
的所有现有方法。
- 我想在 class 实例化时设置一些额外的属性,这些属性稍后将用于定义我可以在 subclass.
上调用的额外方法
这是一个片段:
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)
使用 SubFrame
可以一步指定频率:
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>
这是怎么回事,我该如何避免?
此外,我自称使用 copied 我不太理解的代码。上面 __init__
中发生了什么?这里有必要用args/kwargs和pop
吗? (为什么我不能像往常一样只指定参数?)
我会添加到警告中。不是我要劝阻你,我实际上为你的努力鼓掌。
但是,这不会是您关于正在发生的事情的最后一个问题。
也就是说,一旦你 运行:
super(SubFrame, self).__init__(*args, **kwargs)
self
是一个真正的数据框。您通过将另一个数据帧传递给构造函数来创建它。
试试这个作为实验
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
所以观察到的行为是一致的,因为当您通过将另一个数据帧传递给构造函数来构造一个数据帧时,您最终指向相同的对象。
为了回答您的问题,子类化并不是允许原始对象发生变异的原因...它是 pandas 从传递的数据帧构造数据帧的方式。
通过使用副本实例化来避免这种情况
d2 = pd.DataFrame(d1.copy())
__init__
中发生了什么
您想将所有 args
和 kwargs
传递给 pd.DataFrame.__init__
,但为您的子类指定的特定 kwargs
除外。在这种情况下,freq
和 ddof
。 pop
是一种方便的方法,可以在将 kwargs
传递给 pd.DataFrame.__init__
之前获取值并从中删除键
我将如何实施 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')
我正在忽略 warnings 并尝试子 class 一个 pandas DataFrame。我这样做的原因如下:
- 我想保留
DataFrame
的所有现有方法。 - 我想在 class 实例化时设置一些额外的属性,这些属性稍后将用于定义我可以在 subclass. 上调用的额外方法
这是一个片段:
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)
使用 SubFrame
可以一步指定频率:
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>
这是怎么回事,我该如何避免?
此外,我自称使用 copied 我不太理解的代码。上面 __init__
中发生了什么?这里有必要用args/kwargs和pop
吗? (为什么我不能像往常一样只指定参数?)
我会添加到警告中。不是我要劝阻你,我实际上为你的努力鼓掌。
但是,这不会是您关于正在发生的事情的最后一个问题。
也就是说,一旦你 运行:
super(SubFrame, self).__init__(*args, **kwargs)
self
是一个真正的数据框。您通过将另一个数据帧传递给构造函数来创建它。
试试这个作为实验
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
所以观察到的行为是一致的,因为当您通过将另一个数据帧传递给构造函数来构造一个数据帧时,您最终指向相同的对象。
为了回答您的问题,子类化并不是允许原始对象发生变异的原因...它是 pandas 从传递的数据帧构造数据帧的方式。
通过使用副本实例化来避免这种情况
d2 = pd.DataFrame(d1.copy())
__init__
您想将所有 args
和 kwargs
传递给 pd.DataFrame.__init__
,但为您的子类指定的特定 kwargs
除外。在这种情况下,freq
和 ddof
。 pop
是一种方便的方法,可以在将 kwargs
传递给 pd.DataFrame.__init__
我将如何实施 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')