使用pandas数据框时,枚举的行为异常

3

I have a dataframe(df):

df = pd.DataFrame({'a':[1],'l':[2],'m':[3],'k':[4],'s':[5],'f':[6]},index=[0])

我正在对行使用枚举。

res = [tuple(x) for x in enumerate(df.values)]
print(res)
>>> [(1, 1, 6, 4, 2, 3, 5)]  ### the elements are int type

现在,当我更改数据框df的一个列的数据类型时:

df2 = pd.DataFrame({'a':[1],'l':[2],'m':[3],'k':[4],'s':[5.5],'f':[6]},index=[0])

再次使用enumerate,我得到:

res2 = [tuple(x) for x in enumerate(df2.values)]
print(res2)
>>> [(1, 1.0, 6.0, 4.0, 2.0, 3.0, 5.5)]  ### the elements data type has changed 

我不明白为什么?
另外,我正在寻找一种解决方案,其中必须以其自身的数据类型获取它。例如:
res = [(1, 1, 6, 4, 2, 3, 5.5)]

我该如何达到这个目标?

3个回答

4
这与enumerate无关,那是一个干扰项。问题在于你正在寻找混合类型输出,而 Pandas 更喜欢存储同质数据。

Pandas 不建议您寻找的是不建议使用的。您的数据类型应为 intfloat,而不是组合。这会影响性能,因为唯一简单的替代方案是使用object dtype 系列,其中只允许在 Python 时间内进行操作。转换为 object dtype 是低效的。

所以这里是你可以做的:

res2 = df2.astype(object).values.tolist()[0]

print(res2)

[1, 6, 4, 2, 3, 5.5]

一个避免进行对象转换的方法是:
from itertools import chain
from operator import itemgetter, methodcaller

iter_series = map(itemgetter(1), df2.items())
res2 = list(chain.from_iterable(map(methodcaller('tolist'), iter_series)))

[1, 6, 4, 2, 3, 5.5]

性能基准测试

如果你想要一个元组列表作为输出,每行一个元组,则基于系列的解决方案执行效果更好:

# Python 3.6.0, Pandas 0.19.2

df2 = pd.DataFrame({'a':[1],'l':[2],'m':[3],'k':[4],'s':[5.5],'f':[6]},index=[0])

from itertools import chain
from operator import itemgetter, methodcaller

n = 10**5
df2 = pd.concat([df2]*n)

def jpp_series(df2):
    iter_series = map(itemgetter(1), df2.items())
    return list(zip(*map(methodcaller('tolist'), iter_series)))

def jpp_object1(df2):
    return df2.astype(object).values.tolist()

def jpp_object2(df2):
    return list(map(tuple, df2.astype(object).values.tolist()))

assert jpp_series(df2) == jpp_object2(df2)

%timeit jpp_series(df2)   # 39.7 ms per loop
%timeit jpp_object1(df2)  # 43.7 ms per loop
%timeit jpp_object2(df2)  # 68.2 ms per loop

1
看起来他们想把它放在一个元组中。性能在这里并不重要... - cs95
@coldspeed,这正是重点所在...通常很少有理由“想要做”OP想要做的事情... astype(object)不高效。 - jpp
确实不是这样,但我可以想到一些情况,我更喜欢我的整数保持为整数...在这种情况下,我认为建议使用这种方法没有任何问题。 - cs95
@coldspeed,有可能..我已经提供了一种不需要object数据类型的解决方案。你也许可以想到更巧妙的方法来实现 :) - jpp

3
问题在于调用 df2.values 将导致返回 df2 的数据作为一个具有单一 dtype 的 numpy 数组,其中所有整数也被强制转换为浮点数。
您可以通过操作 object 数组来防止这种强制转换。
使用 astype(object) 将底层 numpy 数组转换为对象并防止类型强制转换:
>>> [(i, *x) for i, x in df2.astype(object).iterrows()]
[(0, 1, 2, 3, 4, 5.5, 6)]

或者,

>>> [(i, *x) for i, x in enumerate(df2.astype(object).values)]
[(0, 1, 2, 3, 4, 5.5, 6)]

或者,在旧版本中,

>>> [(i,) + tuple(x) for i, x in enumerate(df2.astype(object).values)]
[(0, 1, 2, 3, 4, 5.5, 6)]

2

您的 df2 具有混合数据类型:

In [23]: df2 = pd.DataFrame({'a':[1],'l':[2],'m':[3],'k':[4],'s':[5.5],'f':[6]},index=[0])
    ...:

In [24]: df2.dtypes
Out[24]:
a      int64
f      int64
k      int64
l      int64
m      int64
s    float64
dtype: object

因此,使用.values将会“向上转换”为最低公共分母。从文档中可以看到:

dtype将是较低的公共分母dtype(隐式向上转换);也就是说,如果dtypes(甚至是数字类型)混合在一起,那么将选择适应所有dtypes的dtype。 如果您不处理块,则请小心使用此功能。

看起来实际上你只需要.itertuples:
In [25]: list(df2.itertuples())
Out[25]: [Pandas(Index=0, a=1, f=6, k=4, l=2, m=3, s=5.5)]

请注意,这个方法会方便地返回一个namedtuple对象的列表。如果你只需要普通元组,请使用map函数将其转换为普通元组。保留html标签。
In [26]: list(map(tuple, df2.itertuples()))
Out[26]: [(0, 1, 6, 4, 2, 3, 5.5)]

但实际上并不需要这样做。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接