在连接中传播pandas系列元数据

4 投票
1 回答
1225 浏览
提问于 2025-04-18 03:37

我想给一系列数据框(特别是原始文件名)添加一些元数据,这样在合并两个数据框后,我就能看到每个系列的数据来自哪里。

我在GitHub上看到了一些关于_metadata的问题(在这里在这里),其中包括一些与当前的_metadata属性有关的问题(在这里),但在pandas的文档中没有找到相关内容。

到目前为止,我可以修改_metadata属性,理论上可以保存元数据,但在合并后却出现了AttributeError的错误。

df1 = pd.DataFrame(np.random.randint(0, 4, (6, 3)))
df2 = pd.DataFrame(np.random.randint(0, 4, (6, 3)))
df1._metadata.append('filename')
df1[df1.columns[0]]._metadata.append('filename')

for c in df1:
    df1[c].filename = 'fname1.csv'
    df2[c].filename = 'fname2.csv'

df1[0]._metadata  # ['name', 'filename']
df1[0].filename  # fname1.csv
df2[0].filename  # fname2.csv
df1[0][:3].filename  # fname1.csv

mgd = pd.merge(df1, df2, on=[0])
mgd['1_x']._metadata  # ['name', 'filename']
mgd['1_x'].filename  # raises AttributeError

有没有办法保留这些元数据呢?

更新:后记

正如在这里讨论的那样,__finalize__无法跟踪属于数据框的系列,只能跟踪独立的系列。所以目前我会通过维护一个与数据框关联的元数据字典来跟踪系列级别的元数据。我的代码如下:

def cust_merge(d1, d2):
    "Custom merge function for 2 dicts"
    ...

def finalize_df(self, other, method=None, **kwargs):
    for name in self._metadata:
        if method == 'merge':
            lmeta = getattr(other.left, name, {})
            rmeta = getattr(other.right, name, {})
            newmeta = cust_merge(lmeta, rmeta)
            object.__setattr__(self, name, newmeta)
        else:
            object.__setattr__(self, name, getattr(other, name, None))
    return self

df1.filenames = {c: 'fname1.csv' for c in df1}
df2.filenames = {c: 'fname2.csv' for c in df2}
pd.DataFrame._metadata = ['filenames']
pd.DataFrame.__finalize__ = finalize_df

1 个回答

5

我觉得像这样的做法应该是可行的(如果不行,请提交一个bug报告,因为虽然这个方法是支持的,但有点前沿,也就是说,可能连接的方法并不总是调用这个。这一点还没有经过充分测试)。

想了解更多详细的例子或修复,可以查看这个问题

DataFrame._metadata = ['name','filename']


def __finalize__(self, other, method=None, **kwargs):
    """
    propagate metadata from other to self

    Parameters
    ----------
    other : the object from which to get the attributes that we are going
        to propagate
    method : optional, a passed method name ; possibly to take different
        types of propagation actions based on this

    """

    ### you need to arbitrate when their are conflicts

    for name in self._metadata:
        object.__setattr__(self, name, getattr(other, name, None))
    return self

    DataFrame.__finalize__ = __finalize__

这样做是用你自己的自定义方法替换了DataFrame的默认结束方法。在我指出的地方,你需要写一些代码来处理冲突。这就是为什么默认情况下不这样做的原因,比如说frame1的名字是'foo',而frame2的名字是'bar',当方法是__add__时,你该怎么处理呢?其他方法又该怎么办呢?如果你尝试了这个方法,记得告诉我们结果如何。

这个替换只适用于DataFrame(如果你想的话,也可以简单地执行默认操作),默认操作是将其他对象的内容传递给自己;你也可以在某些特殊情况下不设置任何东西。

这个方法是为了在子类中被重写的,所以你在这里进行的是“猴子补丁”,而不是创建子类,因为大多数情况下创建子类会显得过于复杂。

撰写回答