【问题标题】:Applying function to columns of a Pandas DataFrame, conditional on data type将函数应用于 Pandas DataFrame 的列,以数据类型为条件
【发布时间】:2019-08-06 09:18:01
【问题描述】:

我想使用通用模式将函数应用于 Pandas DataFrame 中的每一列,但该函数应根据列数据类型工作。

听起来很简单。但是我在测试数据类型时发现了一个奇怪的行为,我在文档中的任何地方都找不到或在谷歌上搜索它的原因。

考虑这个重复:

import pandas as pd

toydf = pd.DataFrame(dict(
    A = [1, 2, 3],
    B = [1.1, 1.2, 1.3],
    C = ['1', '2', '3'],
    D = [True, True, False]
))

单独检查它们是dtype('int64'), dtype('float64'), dtype('O'), dtype('bool')的dtypes

但如果我使用apply 函数,则传递给该函数的所有列都是dtype: object。

def dtype_fn(the_col):
    print(the_col)
    return(the_col.dtype)

toydf.apply(dtype_fn)

toydf.apply(dtype_fn)
0    1
1    2
2    3
Name: A, dtype: object
0    1.1
1    1.2
2    1.3
Name: B, dtype: object
0    1
1    2
2    3
Name: C, dtype: object
0     True
1     True
2    False
Name: D, dtype: object
Out[167]: 
A    object
B    object
C    object
D    object
dtype: object

这是为什么?,我做错了什么?,为什么列不保留原始数据类型?

这是一种可行并产生我想要的输出的方法:(但出于封装原因,我不喜欢它)

def dtype_fn2(col_name):
    return(toydf[col_name].dtype)

[dtype_fn2(col) for col in toydf.columns]

Out[173]: [dtype('int64'), dtype('float64'), dtype('O'), dtype('bool')]

【问题讨论】:

  • 在阅读更多内容后,当您对这些列执行应用时,由于此处存在多个不同类型的列,pandas 将在这些列中选择“公分母”。在您的示例中,如果您删除“字符串”和“布尔”列,您将停止将“对象”作为您的类型并获得 float64。话虽如此,我不知道如何解决这个问题,但由于这次冒险,我今天学到了一些新东西:)

标签: python pandas


【解决方案1】:

这个comment 是正确的。此行为是设计使然。对于给定的所有 dtype,Pandas “应用”类型层次结构中最高的类型。

考虑将函数仅应用于“A”,

df[['A']].apply(dtype_fn)
int64

A    int64
dtype: object

同样,只有“A”和“B”,

df[['A', 'B']].apply(dtype_fn)
float64
float64

A    float64
B    float64
dtype: object

由于您有多种类型,包括原始 DataFrame 中的字符串,它们的通用类型都是 object。


现在这解释了行为,但我仍然需要解决这个问题。 Pandas 提供了一个有用的方法:Series.infer_objects,它推断 dtype 并执行“软转换”。

如果你真的需要函数中的类型,你可以在调用dtype之前进行软转换。这会产生预期的结果:

def dtype_fn(the_col):
     the_col = the_col.infer_objects()
     print(the_col.dtype)

     return(the_col.dtype)

df.apply(dtype_fn)
int64
float64
object
bool

A      int64
B    float64
C     object
D       bool
dtype: object

【讨论】:

  • 酷,感谢@coldspeed 将我们介绍给Series.infer_objects,这确实解决了我的问题。我不知道这个 fn,但即使我看到了,可能我也不会认为这是修复。鉴于它的名字,我会假设它会推断列 C 为 dtype int64,因为它只有整数。但事实证明,意识到这一列最初是一个字符串并适当地返回一个 dtype: object 是足够聪明的,即使它只有整数值。不知道为什么会这样以及它是如何做到的,所以也许我必须仔细研究一下。谢谢!
  • @HernandoCasas 实际上,infer_objects 不会解析系列中的值来推断它的类型,而只会检查每个值的 dtypes 并确定标量矢量化 dtype(int、float、datetime 等) ) 可以被赋值,也可以是对象。
  • 太棒了,谢谢。检查每个值的dtype 是否会降低性能?只是想知道它是否类似于在数据帧上调用mean 与mean(only numeric)。我最近读到,如果有字符串列(对象dtype),前者会慢得多,因为Pandas 无法提前知道底层数据类型(也可能是python 数字类型;虽然不是numpy)然后它无论如何都会尝试计算对象列的平均值,并最终连接字符串列,只是发现它不能被 n 除,然后才丢弃该列
  • @HernandoCasas 如果您担心性能,您可以在应用时将 df.dtypes.to_dict() 作为参数传递给函数...
【解决方案2】:

dtype_fn 的实际输入是 Pandas Series 对象。您可以通过稍微修改您的方法来访问底层类型。

def dtype_fn(the_col):
    print(the_col.values.dtype)
    return(the_col.values.dtype)

有关为什么会这样的更多信息,您可以查看answer。上面写着

这不是错误,而是由于 numpy dtype 表示: https://docs.scipy.org/doc/numpy/reference/arrays.scalars.html.

【讨论】:

  • 感谢您的回答和指向其他 SO 问题的指针。我实际上已经看到了这个答案,但我认为这是一个稍微不同的问题,因为它们正在处理非本地 numpy/pandas 数据类型。就我而言,我使用的是内置的 numpy/pandas 数据类型。我尝试了您的解决方案,在获取值后修改函数以获取 dtype,但我仍然获得了应该是 int64 或 float64 的对象 dtype。
  • 是的,函数的输入是 Pandas Series 对象。这也是为什么我认为简单地获取the_col.dtype 应该可以工作的原因。因为如果我单独执行,我会得到 Pandas 系列对象,就像 toydf["A"],其 type type(toy["A"]) 确实是 pandas.core.series.Series,但它是 dtype toy["A"].dtype 提供我感兴趣的数据类型 (int64)
  • 嗯,是的,你是对的。奇怪,我非常确定这会起作用,我什至没有想过自己运行它!我猜你今天教会了我一些新东西:)
【解决方案3】:

您希望在数据帧上使用 apply fn,但是您忘记了基本类型转换(大多数 OOP 语言中的一个问题)。快速修复如下:

def selectiveapply(row):
    return(type(row[0]))
toydf=toydf.T
toydf["type"]=toydf.apply(selectiveapply,axis=1)

apply 也可以通过设置axis=0 逐列应用。玩弄一下这些函数,你最终会得到答案。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-09-26
    • 1970-01-01
    • 2019-04-10
    • 1970-01-01
    • 2020-07-02
    • 1970-01-01
    • 1970-01-01
    • 2021-08-23
    相关资源
    最近更新 更多