向熊猫数据框架添加元信息/元数据

有没有可能给熊猫数据框架添加一些元信息/元数据?

例如,用于测量数据的仪器的名称、负责的仪器等。

一个变通方法是创建一个包含该信息的列,但是在每一行中存储一条信息似乎是浪费!

66961 次浏览

当然,像大多数 Python 对象一样,您可以将新属性附加到 pandas.DataFrame:

import pandas as pd
df = pd.DataFrame([])
df.instrument_name = 'Binky'

然而,请注意,虽然您可以将属性附加到 DataFrame,但是在 DataFrame 上执行的操作(例如 groupbypivotjoinloc等等)可能会返回一个新的 DataFrame 没有附加的元数据。大熊猫还没有一个强有力的 繁殖 附加到数据框架的元数据方法。

保留元数据 在一个文件里是可能的。您可以找到如何在 HDF5文件 给你中存储元数据的示例。

没有。尽管您可以像@unutbu 提到的那样将包含元数据的属性添加到 DataFrame 类中,但是许多 DataFrame 方法返回一个新的 DataFrame,因此您的元数据将丢失。如果需要操作数据框架,那么最好的选择是将元数据和 DataFrame 封装在另一个类中。请参见 GitHub: https://github.com/pydata/pandas/issues/2485上的讨论

目前有一个打开的 撤回请求来添加一个 MetaDataFrame 对象,这将更好地支持元数据。

我自己也遇到了这个问题。从0.13开始,DataFrames 具有一个 _ meta 属性,该属性可以通过返回新 DataFrames 的函数持久化。在序列化过程中似乎也没有什么问题(我只试过 json,但是我想 hdf 也被覆盖了)。

说到这里,我认为如果您需要元数据在 I/O 上持久化,这可能会有所帮助。有一个相对较新的包,名为 H5io,我一直在使用它来完成这个任务。

它应该可以让你从 HDF5快速读/写一些常见的格式,其中之一是数据框架。因此,例如,您可以将一个数据框架放入字典中,并将元数据作为字段包含在字典中。例如:

save_dict = dict(data=my_df, name='chris', record_date='1/1/2016')
h5io.write_hdf5('path/to/file.hdf5', save_dict)
in_data = h5io.read_hdf5('path/to/file.hdf5')
df = in_data['data']
name = in_data['name']
etc...

另一个选择是研究像 X 光这样的项目,它在某些方面更复杂,但我认为它确实允许您使用元数据,并且很容易转换为 DataFrame。

正如在其他回答和评论中提到的,_metadata不是公共 API 的一部分,因此在生产环境中使用它肯定不是一个好主意。但是你仍然可能想在研究原型中使用它,如果它停止工作,你可以替换它。现在它与 groupby/apply协同工作,这很有帮助。这是一个例子(我在其他答案中找不到) :

df = pd.DataFrame([1, 2, 2, 3, 3], columns=['val'])
df.my_attribute = "my_value"
df._metadata.append('my_attribute')
df.groupby('val').apply(lambda group: group.my_attribute)

产出:

val
1    my_value
2    my_value
3    my_value
dtype: object

正如@holdgraf 所提到的,我发现 Xarray是一个非常好的工具,可以在比较数据和绘制结果时附加元数据。

在我的工作中,我们经常比较几个固件修订和不同测试场景的结果,添加这些信息就像这样简单:

df = pd.read_csv(meaningless_test)
metadata = {'fw': foo, 'test_name': bar, 'scenario': sc_01}
ds = xr.Dataset.from_dataframe(df)
ds.attrs = metadata

将任意属性附加到 DataFrame 对象的最佳答案是好的,但是如果您使用字典、列表或元组,它将发出一个错误“ Panda 不允许通过新属性名创建列”。下面的解决方案适用于存储任意属性。

from types import SimpleNamespace
df = pd.DataFrame()
df.meta = SimpleNamespace()
df.meta.foo = [1,2,3]

我也遇到了同样的问题,我使用了一个变通方法,从一本带有元数据的字典中创建了一个新的、更小的 DF:

    meta = {"name": "Sample Dataframe", "Created": "19/07/2019"}
dfMeta = pd.DataFrame.from_dict(meta, orient='index')

这个 dfMeta 然后可以保存在您的原始 DF 在腌菜等

关于使用 pickle 保存和检索多个数据帧,请参阅 在 pickle 文件中保存和加载多个对象?(Lutz 的答案)

至于熊猫1.0,可能更早,现在有一个 Dataframe.attrs属性。这是试验性的,但这可能是你将来想要的。 例如:

import pandas as pd
df = pd.DataFrame([])
df.attrs['instrument_name'] = 'Binky'

给你的文件里找到它。

to_parquetfrom_parquet试验一下,它似乎不能持久,所以一定要用你的用例检查一下。

我一直在寻找一个解决方案,发现熊猫框架有属性 attrs

pd.DataFrame().attrs.update({'your_attribute' : 'value'})
frame.attrs['your_attribute']

这个属性将始终坚持您的框架,无论何时您通过它!

向熊猫添加原始属性(例如 df.my_metadata = "source.csv")是 没有的一个好主意。

即使在最新版本(python3.8上是1.2.4)中,这样做也会在使用 read_csv之类的非常简单的操作时随机导致 Segfault。这很难调试,因为 read_csv可以正常工作,但是稍后(看起来是随机的)您会发现数据帧已经从内存中释放。

与熊猫有关的 cpython 扩展似乎对数据框架的数据布局做出了非常明确的假设。

attrs是当前使用元数据属性的唯一安全方法: Https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas

例如:。

df.attrs.update({'my_metadata' : "source.csv"})

Attrs 在所有场景中的行为方式尚未完全确定。在本期中,您可以帮助提供关于 attrs的预期行为的反馈: https://github.com/pandas-dev/pandas/issues/28283

参考 定义原始属性(关于熊猫的官方文件)部分,如果 pandas.DataFrame中的 子类化是一个选项,请注意:

要让原始数据结构具有附加属性,应该让 pandas知道添加了哪些属性。

因此,你可以做的事情-其中 MetaedDataFrame的名称是任意选择的-是

class MetaedDataFrame(pd.DataFrame):
"""s/e."""
_metadata = ['instrument_name']


@property
def _constructor(self):
return self.__class__


# Define the following if providing attribute(s) at instantiation
# is a requirement, otherwise, if YAGNI, don't.
def __init__(
self, *args, instrument_name: str = None, **kwargs
):
super().__init__(*args, **kwargs)
self.instrument_name = instrument_name

然后用您的(_metadata预先指定的)属性实例化数据框架

>>> mdf = MetaedDataFrame(instrument_name='Binky')
>>> mdf.instrument_name
'Binky'

甚至在实例化之后

>>> mdf = MetaedDataFrame()
>>> mdf.instrument_name = 'Binky'
'Binky'

在没有任何警告的情况下(截至2021/06/15) : 序列化~.copy的工作就像魔法一样。此外,这种方法允许丰富您的 API,例如,通过向 MetaedDataFrame添加一些基于 instrument_name的成员,如 物业(或方法) :

    [...]
    

@property
def lower_instrument_name(self) -> str:
if self.instrument_name is not None:
return self.instrument_name.lower()


[...]
>>> mdf.lower_instrument_name
'binky'

... 但这已经超出了这个问题的范围...

根据 Pandas.pydata.org的说法,对于那些希望将数据存储在 HDFStore 中的用户,推荐的方法是:

import pandas as pd


df = pd.DataFrame(dict(keys=['a', 'b', 'c'], values=['1', '2', '3']))
df.to_hdf('/tmp/temp_df.h5', key='temp_df')
store = pd.HDFStore('/tmp/temp_df.h5')
store.get_storer('temp_df').attrs.attr_key = 'attr_value'
store.close()