将修饰的 DataFrame 与所有 pandas 函数相关联

associate decorated DataFrame with all pandas functions

我想为我的 DataFrame 添加一个唯一的 ID,我基本上通过使用我在这里找到的东西成功了,Python Class Decorator. I know from here https://github.com/pydata/pandas/issues/2485 添加自定义元数据还没有明确支持,但装饰器似乎是一种解决方法。

我修饰的 DataFrames return 新的和类似修饰的 DataFrame 当我使用复制和 groupby.agg 等方法时。我怎样才能拥有 "all" pandas 功能,如 pd.DataFrame() 或 pd.read_csv return 我装饰过的 DataFrames 而不是原始的、未装饰的 DataFrames 而不装饰每个 pandas 单独运作?即,如何让我装饰过的 DataFrames 替换现有的 DataFrames?

这是我的代码。首先,我有一个增强的 pandas 模块,wrapPandas.py.

from pandas import *
import numpy as np

def addId(cls):

    class withId(cls):

        def __init__(self, *args, **kargs):
            super(withId, self).__init__(*args, **kargs)
            self._myId = np.random.randint(0,99999)

    return withId

pandas.core.frame.DataFrame = addId(pandas.core.frame.DataFrame)

运行 下面的代码片段显示了我的 DataFrame return 当我使用 .copy() 和 .groupby().agg() 等方法时装饰的 DataFrame。然后,我将通过展示 pandas 函数(例如 pd.DataFrame 不会 return 我的装饰数据帧来跟进(遗憾但并不令人惊讶)。

编辑:根据 Jonathan Eunice 的回复添加了导入语句。

import wrapPandas as pd

d = {
    'strCol': ['A', 'B', 'A', 'C', 'B', 'B', 'A', 'C', 'A'], 
    'intCol': [6,3,8,6,7,3,9,2,6], 
}

#create "decorated" DataFrame
dfFoo = pd.core.frame.DataFrame.from_records(d)
print("dfFoo._myId = {}".format(dfFoo._myId))

#new DataFrame with new ._myId
dfBat = dfFoo.copy()
print("dfBat._myId = {}".format(dfBat._myId))

#new binding for old DataFrame, keeps old ._myId
dfRat = dfFoo
print("dfRat._myId = {}".format(dfRat._myId))

#new DataFrame with new ._myId
dfBird = dfFoo.groupby('strCol').agg({'intCol': 'sum'})
print("dfBird._myId = {}".format(dfBird._myId))

#all of these new DataFrames have the same type, "withId"
print("type(dfFoo) = {}".format(type(dfFoo)))

这会产生以下结果。

dfFoo._myId = 66622
dfBat._myId = 22527
dfRat._myId = 66622
dfBird._myId = 97593
type(dfFoo) = <class 'wrapPandas.withId'>

还有悲伤的部分。 dfBoo._myId 当然会引发 AttributeError.

#create "stock" DataFrame
dfBoo = pd.DataFrame(d)
print(type(dfBoo))

#doesn't have a ._myId (I wish it did, though)
print(dfBoo._myId)

将您的猴子补丁修改为:

pd.DataFrame = pandas.core.frame.DataFrame = addId(pandas.core.frame.DataFrame)

即所以你是 "latching on" 或 "monkey patching" 两个不同的名字。 考虑到 pandas.core.frame.DataFrame is pd.DataFrame,这种双重赋值的需求可能看起来很奇怪。但是你实际上并没有修改 DataFrame class。您正在注入代理 class。无论对代理的引用是什么。那些直接到原始class的没有得到代理行为。通过将您可能想要使用的所有名称指向代理来更改它。 下面是它看起来更直观的样子:

我假设您的文件中某处还有一个未显示的 import pandas as pd,否则您对 dfBoo 的定义将失败并显示 NameError: name 'pd' is not defined

出于这样的原因,猴子补丁是危险的。你在注入东西......而且不可能知道你是 "caught all the references" 还是 "patched everything you need to." 我不能保证代码中不会有其他调用来处理比以下级别更低的结构这个名字重新编排不会生效。但是对于显示的代码,它有效!

更新 您稍后询问了如何为 pd.read_csv 进行这项工作。好吧,那是您可能需要猴子补丁的另一个地方。在这种情况下,将上面的补丁代码修改为:

pd.DataFrame = pandas.io.parsers.DataFrame = pandas.core.frame.DataFrame = addId(pandas.core.frame.DataFrame)

pandas.io.parsers.DataFrame 中修补 DataFrame 的定义可以解决 read_csv 的问题。同样的警告适用:可能有(即可能是)更多的用途,您需要追踪以实现全面覆盖。