Python (pandas): 使用装饰器使用 pandas API
Python (pandas): Using decorators using pandas API
我在 Python 上对装饰器和 类 还很陌生,但我想问一下是否有更好的方法来装饰 pandas 对象。举个例子,我编写了以下代码来创建两个方法——lisa 和 wil:
import numpy as np
import pandas as pd
test = np.array([['john', 'meg', 2.23, 6.49],
['lisa', 'wil', 9.67, 8.87],
['lisa', 'fay', 3.41, 5.04],
['lisa', 'wil', 0.58, 6.12],
['john', 'wil', 7.31, 1.74]],
)
test = pd.DataFrame(test)
test.columns = ['name1','name2','scoreA','scoreB']
@pd.api.extensions.register_dataframe_accessor('abc')
class ABCDataFrame:
def __init__(self, pandas_obj):
self._obj = pandas_obj
@property
def lisa(self):
return self._obj.loc[self._obj['name1'] == 'lisa']
@property
def wil(self):
return self._obj.loc[self._obj['name2'] == 'wil']
示例输出如下:
test.abc.lisa.abc.wil
name1 name2 scoreA scoreB
1 lisa wil 9.67 8.87
3 lisa wil 0.58 6.12
我有两个问题。
首先,在实践中,我创建了两个以上的方法,并且需要在同一行中调用其中的许多方法。有没有办法让 test.lisa.wil
到 return 与上面我写的 test.abc.lisa.abc.wil
相同的输出,因为前者可以让我不必每次都输入 abc
?
其次,如果还有其他suggestions/resources装饰pandas DataFrames,请告诉我。
如果你想用 test.lisa.wil
获取数据,我认为使用包装器 class 比装饰器更合适。另外,我个人更喜欢 test.access(name1='lisa', name2='wil')
之类的东西来访问数据。
这里有一个关于如何完成它的例子:
import numpy as np
import pandas as pd
test = np.array([['john', 'meg', 2.23, 6.49],
['lisa', 'wil', 9.67, 8.87],
['lisa', 'fay', 3.41, 5.04],
['lisa', 'wil', 0.58, 6.12],
['john', 'wil', 7.31, 1.74]],
)
test = pd.DataFrame(test)
test.columns = ['name1','name2','scoreA','scoreB']
class WrapDataFrame(pd.DataFrame):
def access(self, **kwargs):
result = self
for key, val in kwargs.items():
result = result.loc[result[key] == val]
return WrapDataFrame(result)
@property
def lisa(self):
return WrapDataFrame(self.loc[self['name1'] == 'lisa'])
@property
def wil(self):
return WrapDataFrame(self.loc[self['name2'] == 'wil'])
wdf = WrapDataFrame(test)
# First way to access
print(wdf.lisa.wil)
# Second way to access (recommended)
print(wdf.access(name1='lisa', name2='wil'))
# Third way to access (easiest to do programaticaly)
data_filter = {'name1': 'lisa', 'name2': 'wil'}
print(wdf.access(**data_filter))
注意 class WrapDataFrame
继承了 pd.DataFrame
,因此 pandas 数据帧的所有操作都应该兼容。
您可以使用 pandas-flavor 库执行此操作,它允许您使用其他方法扩展 DataFrame
class。
import pandas as pd
import pandas_flavor as pf
# Create test DataFrame as before.
test = pd.DataFrame([
['john', 'meg', 2.23, 6.49],
['lisa', 'wil', 9.67, 8.87],
['lisa', 'fay', 3.41, 5.04],
['lisa', 'wil', 0.58, 6.12],
['john', 'wil', 7.31, 1.74]
], columns=['name1', 'name2', 'scoreA', 'scoreB'])
# Register new methods.
@pf.register_dataframe_method
def lisa(df):
return df.loc[df['name1'] == 'lisa']
@pf.register_dataframe_method
def wil(df):
return df.loc[df['name2'] == 'wil']
现在可以将它们视为方法,而无需中间 .abc
访问器。
test.lisa()
# name1 name2 scoreA scoreB
# 1 lisa wil 9.67 8.87
# 2 lisa fay 3.41 5.04
# 3 lisa wil 0.58 6.12
test.lisa().wil()
# name1 name2 scoreA scoreB
# 1 lisa wil 9.67 8.87
# 3 lisa wil 0.58 6.12
更新
既然你有很多这样的,也可以定义一个通用的过滤方法,然后在一些循环中调用它。
def add_method(key, val, fn_name=None):
def fn(df):
return df.loc[df[key] == val]
if fn_name is None:
fn_name = f'{key}_{val}'
fn.__name__ = fn_name
fn = pf.register_dataframe_method(fn)
return fn
for name1 in ['john', 'lisa']:
add_method('name1', name1)
for name2 in ['fay', 'meg', 'wil']:
add_method('name2', name2)
然后这些就可以作为方法使用,就好像您直接定义了方法一样。请注意,我在列名(name1
或 name2
)前添加了前缀,以使其更加清晰。那是可选的。
test.name1_john()
# name1 name2 scoreA scoreB
# 0 john meg 2.23 6.49
# 4 john wil 7.31 1.74
test.name1_lisa()
# name1 name2 scoreA scoreB
# 1 lisa wil 9.67 8.87
# 2 lisa fay 3.41 5.04
# 3 lisa wil 0.58 6.12
test.name2_fay()
# name1 name2 scoreA scoreB
# 2 lisa fay 3.41 5.04
更新 2
注册的方法也可以有参数。所以另一种方法是为每列创建一个这样的方法,并将值作为参数。
@pf.register_dataframe_method
def name1(df, val):
return df.loc[df['name1'] == val]
@pf.register_dataframe_method
def name2(df, val):
return df.loc[df['name2'] == val]
test.name1('lisa')
# name1 name2 scoreA scoreB
# 1 lisa wil 9.67 8.87
# 2 lisa fay 3.41 5.04
# 3 lisa wil 0.58 6.12
test.name1('lisa').name2('wil')
# name1 name2 scoreA scoreB
# 1 lisa wil 9.67 8.87
# 3 lisa wil 0.58 6.12
您可以使用class
来帮助您。 (虽然这与真正的装饰功能关系不大)。
查看以下内容:
class DecoratorDF:
def __init__(self, df: pd.DataFrame, n_layer: int = 0):
self.df = df
self.layer = n_layer
def __repr__(self):
return str(self.df)
def __getattr__(self, item):
layer = self.df.columns[self.layer]
return DecoratorDF(self.df.loc[self.df[layer] == item], self.layer + 1)
my_df = DecoratorDF(
pd.DataFrame([['A', 'B', 'C'],
['A', 'B', 'D'],
['E', 'F', 'G'],
], columns=['name1', 'name2', 'name3'])
)
print(my_df.A.B)
print(my_df.A.B.C)
name1 name2 name3
0 A B C
1 A B D
name1 name2 name3
0 A B C
完整示例
import numpy as np
import pandas as pd
class DecoratorDF:
def __init__(self, df: pd.DataFrame, n_layer: int = 0):
self.df = df
self.layer = n_layer
def __repr__(self):
return str(self.df)
def __getattr__(self, item):
layer = self.df.columns[self.layer]
return DecoratorDF(self.df.loc[self.df[layer] == item], self.layer + 1)
test_data = np.array([['john', 'meg', 2.23, 6.49],
['lisa', 'wil', 9.67, 8.87],
['lisa', 'fay', 3.41, 5.04],
['lisa', 'wil', 0.58, 6.12],
['john', 'wil', 7.31, 1.74]],
)
test_df = pd.DataFrame(test_data, columns=['name1', 'name2', 'scoreA', 'scoreB'])
test_df = DecoratorDF(test_df)
df_lisa_and_wil = test_df.lisa.wil
print(df_lisa_and_wil)
df_lisa_and_wil = df_lisa_and_wil.df
print(df_lisa_and_wil.loc[df_lisa_and_wil['scoreA'] == '9.67'])
name1 name2 scoreA scoreB
1 lisa wil 9.67 8.87
3 lisa wil 0.58 6.12
name1 name2 scoreA scoreB
1 lisa wil 9.67 8.87
我在 Python 上对装饰器和 类 还很陌生,但我想问一下是否有更好的方法来装饰 pandas 对象。举个例子,我编写了以下代码来创建两个方法——lisa 和 wil:
import numpy as np
import pandas as pd
test = np.array([['john', 'meg', 2.23, 6.49],
['lisa', 'wil', 9.67, 8.87],
['lisa', 'fay', 3.41, 5.04],
['lisa', 'wil', 0.58, 6.12],
['john', 'wil', 7.31, 1.74]],
)
test = pd.DataFrame(test)
test.columns = ['name1','name2','scoreA','scoreB']
@pd.api.extensions.register_dataframe_accessor('abc')
class ABCDataFrame:
def __init__(self, pandas_obj):
self._obj = pandas_obj
@property
def lisa(self):
return self._obj.loc[self._obj['name1'] == 'lisa']
@property
def wil(self):
return self._obj.loc[self._obj['name2'] == 'wil']
示例输出如下:
test.abc.lisa.abc.wil
name1 name2 scoreA scoreB
1 lisa wil 9.67 8.87
3 lisa wil 0.58 6.12
我有两个问题。
首先,在实践中,我创建了两个以上的方法,并且需要在同一行中调用其中的许多方法。有没有办法让 test.lisa.wil
到 return 与上面我写的 test.abc.lisa.abc.wil
相同的输出,因为前者可以让我不必每次都输入 abc
?
其次,如果还有其他suggestions/resources装饰pandas DataFrames,请告诉我。
如果你想用 test.lisa.wil
获取数据,我认为使用包装器 class 比装饰器更合适。另外,我个人更喜欢 test.access(name1='lisa', name2='wil')
之类的东西来访问数据。
这里有一个关于如何完成它的例子:
import numpy as np
import pandas as pd
test = np.array([['john', 'meg', 2.23, 6.49],
['lisa', 'wil', 9.67, 8.87],
['lisa', 'fay', 3.41, 5.04],
['lisa', 'wil', 0.58, 6.12],
['john', 'wil', 7.31, 1.74]],
)
test = pd.DataFrame(test)
test.columns = ['name1','name2','scoreA','scoreB']
class WrapDataFrame(pd.DataFrame):
def access(self, **kwargs):
result = self
for key, val in kwargs.items():
result = result.loc[result[key] == val]
return WrapDataFrame(result)
@property
def lisa(self):
return WrapDataFrame(self.loc[self['name1'] == 'lisa'])
@property
def wil(self):
return WrapDataFrame(self.loc[self['name2'] == 'wil'])
wdf = WrapDataFrame(test)
# First way to access
print(wdf.lisa.wil)
# Second way to access (recommended)
print(wdf.access(name1='lisa', name2='wil'))
# Third way to access (easiest to do programaticaly)
data_filter = {'name1': 'lisa', 'name2': 'wil'}
print(wdf.access(**data_filter))
注意 class WrapDataFrame
继承了 pd.DataFrame
,因此 pandas 数据帧的所有操作都应该兼容。
您可以使用 pandas-flavor 库执行此操作,它允许您使用其他方法扩展 DataFrame
class。
import pandas as pd
import pandas_flavor as pf
# Create test DataFrame as before.
test = pd.DataFrame([
['john', 'meg', 2.23, 6.49],
['lisa', 'wil', 9.67, 8.87],
['lisa', 'fay', 3.41, 5.04],
['lisa', 'wil', 0.58, 6.12],
['john', 'wil', 7.31, 1.74]
], columns=['name1', 'name2', 'scoreA', 'scoreB'])
# Register new methods.
@pf.register_dataframe_method
def lisa(df):
return df.loc[df['name1'] == 'lisa']
@pf.register_dataframe_method
def wil(df):
return df.loc[df['name2'] == 'wil']
现在可以将它们视为方法,而无需中间 .abc
访问器。
test.lisa()
# name1 name2 scoreA scoreB
# 1 lisa wil 9.67 8.87
# 2 lisa fay 3.41 5.04
# 3 lisa wil 0.58 6.12
test.lisa().wil()
# name1 name2 scoreA scoreB
# 1 lisa wil 9.67 8.87
# 3 lisa wil 0.58 6.12
更新
既然你有很多这样的,也可以定义一个通用的过滤方法,然后在一些循环中调用它。
def add_method(key, val, fn_name=None):
def fn(df):
return df.loc[df[key] == val]
if fn_name is None:
fn_name = f'{key}_{val}'
fn.__name__ = fn_name
fn = pf.register_dataframe_method(fn)
return fn
for name1 in ['john', 'lisa']:
add_method('name1', name1)
for name2 in ['fay', 'meg', 'wil']:
add_method('name2', name2)
然后这些就可以作为方法使用,就好像您直接定义了方法一样。请注意,我在列名(name1
或 name2
)前添加了前缀,以使其更加清晰。那是可选的。
test.name1_john()
# name1 name2 scoreA scoreB
# 0 john meg 2.23 6.49
# 4 john wil 7.31 1.74
test.name1_lisa()
# name1 name2 scoreA scoreB
# 1 lisa wil 9.67 8.87
# 2 lisa fay 3.41 5.04
# 3 lisa wil 0.58 6.12
test.name2_fay()
# name1 name2 scoreA scoreB
# 2 lisa fay 3.41 5.04
更新 2
注册的方法也可以有参数。所以另一种方法是为每列创建一个这样的方法,并将值作为参数。
@pf.register_dataframe_method
def name1(df, val):
return df.loc[df['name1'] == val]
@pf.register_dataframe_method
def name2(df, val):
return df.loc[df['name2'] == val]
test.name1('lisa')
# name1 name2 scoreA scoreB
# 1 lisa wil 9.67 8.87
# 2 lisa fay 3.41 5.04
# 3 lisa wil 0.58 6.12
test.name1('lisa').name2('wil')
# name1 name2 scoreA scoreB
# 1 lisa wil 9.67 8.87
# 3 lisa wil 0.58 6.12
您可以使用class
来帮助您。 (虽然这与真正的装饰功能关系不大)。
查看以下内容:
class DecoratorDF:
def __init__(self, df: pd.DataFrame, n_layer: int = 0):
self.df = df
self.layer = n_layer
def __repr__(self):
return str(self.df)
def __getattr__(self, item):
layer = self.df.columns[self.layer]
return DecoratorDF(self.df.loc[self.df[layer] == item], self.layer + 1)
my_df = DecoratorDF(
pd.DataFrame([['A', 'B', 'C'],
['A', 'B', 'D'],
['E', 'F', 'G'],
], columns=['name1', 'name2', 'name3'])
)
print(my_df.A.B)
print(my_df.A.B.C)
name1 name2 name3
0 A B C
1 A B D
name1 name2 name3
0 A B C
完整示例
import numpy as np
import pandas as pd
class DecoratorDF:
def __init__(self, df: pd.DataFrame, n_layer: int = 0):
self.df = df
self.layer = n_layer
def __repr__(self):
return str(self.df)
def __getattr__(self, item):
layer = self.df.columns[self.layer]
return DecoratorDF(self.df.loc[self.df[layer] == item], self.layer + 1)
test_data = np.array([['john', 'meg', 2.23, 6.49],
['lisa', 'wil', 9.67, 8.87],
['lisa', 'fay', 3.41, 5.04],
['lisa', 'wil', 0.58, 6.12],
['john', 'wil', 7.31, 1.74]],
)
test_df = pd.DataFrame(test_data, columns=['name1', 'name2', 'scoreA', 'scoreB'])
test_df = DecoratorDF(test_df)
df_lisa_and_wil = test_df.lisa.wil
print(df_lisa_and_wil)
df_lisa_and_wil = df_lisa_and_wil.df
print(df_lisa_and_wil.loc[df_lisa_and_wil['scoreA'] == '9.67'])
name1 name2 scoreA scoreB
1 lisa wil 9.67 8.87
3 lisa wil 0.58 6.12
name1 name2 scoreA scoreB
1 lisa wil 9.67 8.87