【问题标题】:Does Pandas allow custom objects as column labels?Pandas 是否允许自定义对象作为列标签?
【发布时间】:2020-12-12 20:19:08
【问题描述】:

在 Pandas 中,我一直使用自定义对象作为列标签,因为它们为特定于列的信息/方法提供了丰富/灵活的功能。例如,您可以设置自定义fmt_fn 来格式化每一列(注意这只是一个示例,我的实际列标签对象更复杂):

In [100]: class Col:
     ...:     def __init__(self, name, fmt_fn):
     ...:         self.name = name
     ...:         self.fmt_fn = fmt_fn
     ...:     def __str__(self):
     ...:         return self.name
     ...:     

In [101]: sec_col = Col('time', lambda val: str(timedelta(seconds=val)).split('.')[0])

In [102]: dollar_col = Col('money', lambda val: '${:.2f}'.format(val))

In [103]: foo = pd.DataFrame(np.random.random((3, 2)) * 1000, columns = [sec_col, dollar_col])

In [104]: print(foo)  # ugly
         time       money
0  773.181402  720.997051
1   33.779925  317.957813
2  590.750129  416.293245

In [105]: print(foo.to_string(formatters = [col.fmt_fn for col in foo.columns]))  # pretty
     time   money
0 0:12:53 $721.00
1 0:00:33 $317.96
2 0:09:50 $416.29

好的,所以我一直很高兴地这样做了一段时间,但是最近我遇到了 Pandas 的一部分,它不支持这一点。具体来说,方法 to_hdf/read_hdf will fail 在具有自定义列标签的 DataFrames 上。这对我来说不是一个交易破坏者。我可以使用 pickle 代替 HDF5,但会损失一些效率。

但更大的问题是,Pandas 通常是否支持自定义对象作为列标签?换句话说,我是应该继续这样使用 Pandas,还是以后会在 Pandas 的其他部分(HDF5 除外)出现这种情况,给我带来未来的痛苦?

PS。附带说明一下,如果您当前不使用自定义对象作为列标签,我不介意您是否也解决列特定信息的问题,例如上面示例中的 fmt_fn。

【问题讨论】:

  • 有趣的问题,因为我从未见过对象作为 DataFrame 中的列传递。我建议不要使用这种用法。如果您需要灵活性,可以保留列名和底层对象的字典。
  • 如果为每个 DataFrame 维护一个与 foo.columns 平行的单独数据结构,而不是简单地将特定于列的数据放入 foo.columns,那将是糟糕的设计 (IMO)。我只会在必要时这样做,即如果 Pandas 真的不支持自定义对象作为列标签。因此我发布了这个问题。
  • 数据框的列只是一个索引。似乎唯一的要求是对象是可散列的。 pandas.pydata.org/pandas-docs/stable/generated/…

标签: python pandas


【解决方案1】:

目前还不可能对DataFrame 的格式进行细粒度控制。例如,请参阅 here 或 here 以了解有关可能性的一些讨论。我敢肯定,经过深思熟虑的 API(和 PR!)会很受欢迎。

在使用自定义对象作为列方面,最大的两个问题可能是序列化和索引语义(例如不能再做df['time'])。

一种可能的解决方法是将您的DataFrame 包装成某种漂亮的打印结构,如下所示:

In [174]: class PrettyDF(object):
     ...:     def __init__(self, data, formatters):
     ...:         self.data = data
     ...:         self.formatters = formatters
     ...:     def __str__(self):
     ...:         return self.data.to_string(formatters=self.formatters)
     ...:     def __repr__(self):
     ...:         return self.__str__()


In [172]: foo = PrettyDF(df, 
                        formatters={'money': '${:.2f}'.format, 
                                    'time': lambda val: str(timedelta(seconds=val)).split('.')[0]})


In [178]: foo
Out[178]: 
     time   money
0 0:13:17 $399.29
1 0:08:48 $122.44
2 0:07:42 $491.72

In [180]: foo.data['time']
Out[180]: 
0    797.699511
1    528.155876
2    462.999224
Name: time, dtype: float64

【讨论】:

  • 正如我在问题帖子中指出的那样,fmt_fn 只是列特定数据的示例。我的实际列标签对象要复杂得多,提供比输出格式更丰富的功能。
  • 就您列出的“两个最大问题”而言:(a) 序列化是我确实遇到的一个问题,这促使我写了这个问题——希望我可以用 pickle 处理它. (b) “不能再做df['time']”在我的书中不会被视为问题,因为'time' 不是列标签对象(只是它的打印表示)——正确的代码是df[sec_col] 并且按预期正常工作。鉴于您上面的 cmets 和 Alexander 的评论,我认为我目前的结论是继续在列标签中使用自定义对象是安全的。谢谢!
  • 我用__str__ 尝试了同样的事情,但它不适用于多索引数据帧。你也有解决方案吗? stackoverflow.com/questions/49563981/…
【解决方案2】:

这已经发布五年了,所以我希望这仍然对某人有所帮助。我已经设法构建了一个对象来保存 pandas 数据框列的元数据,但仍然可以作为常规列访问(或者在我看来)。下面的代码只是涉及到这个的整个类的一部分。

__repr 用于在打印数据框而不是对象时显示对象的名称

__eq 用于检查请求的名称与对象的可用名称 __hash 也用于此过程中 列名需要是可散列的,因为它类似于字典。

这可能不是pythonic的描述方式,但在我看来,它就是这样工作的。

    class ColumnDescriptor:
        def __init__(self, name, **kwargs):
            self.name = name
            [self.__setattr__(n, v) for n, v in kwargs.items()]
    
        def __repr__(self): return self.name
        def __str__(self): return self.name
        def __eq__(self, other): return self.name == other
        def __hash__(self): return hash(self.name)

【讨论】:

    猜你喜欢
    • 2017-08-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-12-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多