使用熊猫数据框时枚举的怪异行为

时间:2018-11-12 09:41:06

标签: python pandas

我有一个数据框(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])

再次使用枚举,我得到:

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 个答案:

答案 0 :(得分:4)

这与enumerate无关,那是一条红鲱鱼。问题是您正在寻找混合类型的输出,而Pandas更喜欢存储同类数据。

不推荐与熊猫一起寻找。您的数据类型应为intfloat,而不是组合。这对性能有影响,因为唯一的直接选择就是使用object dtype系列,该系列仅允许在Python时间内进行操作。转换为object dtype效率不高。

这就是您可以做到的

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

print(res2)

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

一种避免object转换的方法:

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 :(得分: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 :(得分:2)

您的df2具有混合的dtypes:

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将“转换”为最低的公分母。来自the doces

  

dtype将是一个较低的公分母dtype(隐式   cast)也就是说,如果dtypes(甚至是数字类型)是   混合,将容纳所有。搭配使用   关心是否不处理这些块。

看起来就像您实际上只想要.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对象的列表,如果您真的只想要普通元组,请将tuple映射到它:

In [26]: list(map(tuple, df2.itertuples()))
Out[26]: [(0, 1, 6, 4, 2, 3, 5.5)]

但是真的没有必要。