【发布时间】:2013-01-19 05:52:48
【问题描述】:
是否可以向 pandas DataFrame 添加一些元信息/元数据?
例如用于测量数据的仪器名称、负责的仪器等
一种解决方法是使用该信息创建一个列,但在每一行中存储一条信息似乎很浪费!
【问题讨论】:
-
请注意@ryanjdillon 的答案(目前埋在底部附近),其中提到了更新的实验属性“attrs”,这似乎是一个开始,也许
是否可以向 pandas DataFrame 添加一些元信息/元数据?
例如用于测量数据的仪器名称、负责的仪器等
一种解决方法是使用该信息创建一个列,但在每一行中存储一条信息似乎很浪费!
【问题讨论】:
当然,像大多数 Python 对象一样,您可以将新属性附加到 pandas.DataFrame:
import pandas as pd
df = pd.DataFrame([])
df.instrument_name = 'Binky'
但是请注意,虽然您可以将属性附加到 DataFrame,但对 DataFrame 执行的操作(例如 groupby、pivot、join 或 loc 仅举几例)可能会返回一个新的DataFrame 没有附加的元数据。 Pandas 还没有一个强大的传播metadata attached to DataFrames方法。
将元数据保存在文件中 是可能的。您可以找到有关如何将元数据存储在 HDF5 文件 here 中的示例。
【讨论】:
store = pd.HDFStore(...),那么属性可以用store.root._v_attrs.key = value存储。
df = pd.DataFrame(); df.meta = {} 产生 UserWarning: Pandas doesn't allow columns to be created via a new attribute name - see https://pandas.pydata.org/pandas-docs/stable/indexing.html#attribute-access)。 (如果属性已经像df = pd.DataFrame(); df.meta = ''; df.meta = {} 一样创建,则不会给出警告)。
不是真的。尽管您可以像@unutbu 提到的那样将包含元数据的属性添加到 DataFrame 类中,但许多 DataFrame 方法返回一个新的 DataFrame,因此您的元数据会丢失。如果您需要操作数据框,那么最好的选择是将元数据和数据框包装在另一个类中。在 GitHub 上查看此讨论:https://github.com/pydata/pandas/issues/2485
目前有一个开放的pull request 添加一个 MetaDataFrame 对象,这将更好地支持元数据。
【讨论】:
我自己也遇到了这个问题。从 pandas 0.13 开始,DataFrame 上有一个 _metadata 属性,该属性通过返回新 DataFrame 的函数持续存在。似乎也可以在序列化中幸存下来(我只尝试过 json,但我想 hdf 也被覆盖了)。
【讨论】:
_metadata 不是公共 API 的一部分,因此我强烈建议不要依赖此功能。
.attrs 是 xray API 的一部分)
_metadata 实际上是类属性,而不是实例属性。因此,只要模块保持加载状态,新的 DataFrame 实例就会继承以前的实例。不要将_metadata 用于任何事情。 +1 xarray!
很晚才开始这样做,我认为如果您需要元数据在 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...
另一种选择是研究像 xray 这样的项目,它在某些方面更复杂,但我认为它确实允许您使用元数据并且很容易转换为 DataFrame。
【讨论】:
正如其他答案和 cmets 中提到的,_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
【讨论】:
正如@choldgraf 所提到的,我发现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 对象的最佳答案是好的,但是如果您使用字典、列表或元组,它将发出错误“Pandas 不允许通过新属性名称创建列”。以下解决方案适用于存储任意属性。
from types import SimpleNamespace
df = pd.DataFrame()
df.meta = SimpleNamespace()
df.meta.foo = [1,2,3]
【讨论】:
pd.DataFrame._metadata += ["meta"] 。请注意,这部分是 Pandas 的属性,而不是您的特定数据框的属性
df.meta 会触发警告说 Pandas 不允许以这种方式生成新列。
df.meta 是一个简单命名空间,因此不会触发该警告。 Pandas 不会尝试从中构建列。
我遇到了同样的问题,并使用了一种解决方法,即从带有元数据的字典中创建一个新的、更小的 DF:
meta = {"name": "Sample Dataframe", "Created": "19/07/2019"}
dfMeta = pd.DataFrame.from_dict(meta, orient='index')
然后可以将此 dfMeta 与您的原始 DF 一起保存在 pickle 等中
请参阅Saving and loading multiple objects in pickle file?(Lutz 的回答),了解有关使用 pickle 保存和检索多个数据帧的出色回答
【讨论】:
从可能更早的 pandas 1.0 开始,现在有一个 Dataframe.attrs 属性。它是实验性的,但这可能是您将来想要的。
例如:
import pandas as pd
df = pd.DataFrame([])
df.attrs['instrument_name'] = 'Binky'
在文档here 中找到它。
用to_parquet 和from_parquet 尝试这个,它似乎不会持续,所以一定要检查一下你的用例。
【讨论】:
dataclass 作为元数据,然后对DataFrame 进行子类化以使用您分享的帖子中的方法进行加载/转储可能是一个不错的解决方案。
我一直在寻找解决方案,发现pandas框架有attrs的属性
pd.DataFrame().attrs.update({'your_attribute' : 'value'})
frame.attrs['your_attribute']
无论何时传递,此属性都会始终粘在您的框架上!
【讨论】:
使用 pandas 添加原始属性(例如df.my_metadata = "source.csv")不是一个好主意。
即使在最新版本上(python 3.8 上的 1.2.4),在使用 read_csv 之类的非常简单的操作时,这样做也会随机导致段错误。这将很难调试,因为read_csv 可以正常工作,但稍后(似乎是随机的)您会发现数据帧已从内存中释放。
似乎与 pandas 相关的 cpython 扩展似乎对数据帧的数据布局做出了非常明确的假设。
attrs 是目前使用元数据属性的唯一安全方式:
https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.attrs.html
例如
df.attrs.update({'my_metadata' : "source.csv"})
attrs 在所有场景中的行为方式并未完全充实。您可以在此问题中帮助提供有关attrs 预期行为的反馈:https://github.com/pandas-dev/pandas/issues/28283
【讨论】:
参考 Define original properties(official Pandas documentation)部分,如果 subclassing 来自 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-prespecified) 属性实例化您的数据框
>>> mdf = MetaedDataFrame(instrument_name='Binky')
>>> mdf.instrument_name
'Binky'
甚至在实例化之后
>>> mdf = MetaedDataFrame()
>>> mdf.instrument_name = 'Binky'
'Binky'
没有任何警告(截至 2021 年 6 月 15 日):serialization 和 ~.copy 就像一个魅力。此外,这种方法可以丰富您的 API,例如通过向MetaedDataFrame 添加一些基于instrument_name 的成员,例如properties(或方法):
[...]
@property
def lower_instrument_name(self) -> str:
if self.instrument_name is not None:
return self.instrument_name.lower()
[...]
>>> mdf.lower_instrument_name
'binky'
... 但这超出了这个问题的范围 ...
【讨论】:
对于那些希望将数据帧存储在 HDFStore 中的人,根据pandas.pydata.org,推荐的方法是:
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()
【讨论】: