在索引和插入行时防止强制熊猫数据帧

Posted

技术标签:

【中文标题】在索引和插入行时防止强制熊猫数据帧【英文标题】:Prevent coercion of pandas data frames while indexing and inserting rows 【发布时间】:2020-02-20 05:46:38 【问题描述】:

我正在处理单独的 pandas 数据框行,但在索引和插入行时遇到了强制问题。 Pandas 似乎总是希望从混​​合的 int/float 类型强制转换为全浮点类型,而我看不到对这种行为的任何明显控制。

例如,这是一个简单的数据框,aintbfloat

import pandas as pd
pd.__version__  # '0.25.2'

df = pd.DataFrame('a': [1], 'b': [2.2])
print(df)
#    a    b
# 0  1  2.2
print(df.dtypes)
# a      int64
# b    float64
# dtype: object

这是索引一行时的强制问题:

print(df.loc[0])
# a    1.0
# b    2.2
# Name: 0, dtype: float64
print(dict(df.loc[0]))
# 'a': 1.0, 'b': 2.2

这是插入一行时的强制问题:

df.loc[1] = 'a': 5, 'b': 4.4
print(df)
#      a    b
# 0  1.0  2.2
# 1  5.0  4.4
print(df.dtypes)
# a    float64
# b    float64
# dtype: object

在这两种情况下,我都希望 a 列保持为整数类型,而不是强制为浮点类型。

【问题讨论】:

我找到了this,但我找不到问题是否得到有效解决。同时我想你可以这样做:df.loc[[0], df.columns] 重复? .loc indexing changes type & Adding row to pandas DataFrame changes dtype. 听起来像 pd.DataFrame 不支持实例化类型混合? pandas.pydata.org/pandas-docs/stable/reference/api/… dtype 参数仅支持单一类型。 .read_[type] 虽然支持多种数据类型... 【参考方案1】:

经过一番挖掘,这里有一些非常丑陋的解决方法。 (将接受更好的答案。)

一个怪癖found here 是非数字列停止强制,所以这里是如何将一行索引到dict

dict(df.assign(_='').loc[0].drop('_', axis=0))
# 'a': 1, 'b': 2.2

并且插入一行可以通过创建一个包含一行的新数据框来完成:

df = df.append(pd.DataFrame('a': 5, 'b': 4.4, index=[1]))
print(df)
#    a    b
# 0  1  2.2
# 1  5  4.4

这两个技巧都没有针对大数据帧进行优化,因此我将不胜感激!

【讨论】:

你总是可以强制发布附加df['a'] = df.a.astype(mytype)...虽然它仍然很脏,而且可能效率不高。 .astype() 对 float -> integer 很危险;将1.1 更改为1 没有问题,因此在执行此操作之前,您确实需要确保所有值都“类似于整数”。可能最好将pd.to_numericdowncast='integer' 一起使用【参考方案2】:

当您从数据帧获取数据或将数据附加到数据帧并需要保持数据类型相同时,请避免转换为不知道所需数据类型的其他内部结构。

当您执行df.loc[0] 时,它会转换为pd.Series

>>> type(df.loc[0])
<class 'pandas.core.series.Series'>

现在,Series 将只有一个 dtype。从而将int 强制为float

而是将结构保持为pd.DataFrame

>>> type(df.loc[[0]])
<class 'pandas.core.frame.DataFrame'>

选择需要的行作为框架,然后转换为dict

>>> df.loc[[0]].to_dict(orient='records')
['a': 1, 'b': 2.2]

同样,要添加新行,使用 pandas pd.DataFrame.append 函数,

>>> df = df.append(['a': 5, 'b': 4.4]) # NOTE: To append as a row, use []
   a    b
0  1  2.2
0  5  4.4

以上不会引起类型转换,

>>> df.dtypes
a      int64
b    float64
dtype: object

【讨论】:

哇,第二个代码块必须读三遍才能得到它。这是非常微妙的。这比我过去所做的要好得多......遍历最终数据帧并使用正确的数据类型重新分配值(是的,我所做的是一个可怕的解决方案,真的不会扩展。)。跨度> 哦。很高兴它有帮助?@VanBantam【参考方案3】:

问题的根源在于

    熊猫数据帧的索引返回熊猫系列

我们可以看到:

type(df.loc[0])
# pandas.core.series.Series

一个系列只能有一个 dtype,在你的情况下是 int64 或 float64。

我想到了两种解决方法:

print(df.loc[[0]])
# this will return a dataframe instead of series
# so the result will be
#    a    b
# 0  1  2.2

# but the dictionary is hard to read
print(dict(df.loc[[0]]))
# 'a': 0    1
# Name: a, dtype: int64, 'b': 0    2.2
# Name: b, dtype: float64

print(df.astype(object).loc[0])
# this will change the type of value to object first and then print
# so the result will be
# a      1
# b    2.2
# Name: 0, dtype: object

print(dict(df.astype(object).loc[0]))
# in this way the dictionary is as expected
# 'a': 1, 'b': 2.2
    当您将字典附加到数据帧时,它会先将字典转换为 Series,然后再附加。 (所以同样的问题又发生了)

https://github.com/pandas-dev/pandas/blob/master/pandas/core/frame.py#L6973

if isinstance(other, dict):
    other = Series(other)

所以你的绕行实际上是一个可靠的,否则我们可以:

df.append(pd.Series('a': 5, 'b': 4.4, dtype=object, name=1))
#    a    b
# 0  1  2.2
# 1  5  4.4

【讨论】:

使用object 数据类型的好主意!另一种是从头创建对象DataFrame:df = pd.DataFrame('a': [1], 'b': [2.2], dtype=object)【参考方案4】:

一种对数据进行轻微操作的不同方法:

假设您有一个字典(或数据框)列表

lod=['a': [1], 'b': [2.2], 'a': [5], 'b': [4.4]]

每个字典代表一行(注意第二个字典中的列表)。然后您可以通过以下方式轻松创建数据框:

pd.concat([pd.DataFrame(dct) for dct in lod])
   a    b
0  1  2.2
0  5  4.4

并且您维护列的类型。见concat

所以如果你有一个数据框和一个字典列表,你可以使用

pd.concat([df] + [pd.DataFrame(dct) for dct in lod])

【讨论】:

【参考方案5】:

在第一种情况下,您可以使用nullable integer data type。 Series 选择不会强制转换为 float,并且值被放置在 object 容器中。然后正确创建字典,并将基础值存储为np.int64

df = pd.DataFrame('a': [1], 'b': [2.2])
df['a'] = df['a'].astype('Int64')

d = dict(df.loc[0])
#'a': 1, 'b': 2.2

type(d['a'])
#numpy.int64

使用您的语法,这几乎也适用于第二种情况,但这向上转换为object,所以不是很好:

df.loc[1] = 'a': 5, 'b': 4.4
#   a    b
#0  1  2.2
#1  5  4.4

df.dtypes
#a     object
#b    float64
#dtype: object

但是,我们可以对在末尾添加一行的语法进行一些小改动(使用 RangeIndex),现在可以正确处理类型。

df = pd.DataFrame('a': [1], 'b': [2.2])
df['a'] = df['a'].astype('Int64')

df.loc[df.shape[0], :] = [5, 4.4]
#   a    b
#0  1  2.2
#1  5  4.4

df.dtypes
#a      Int64
#b    float64
#dtype: object

【讨论】:

以上是关于在索引和插入行时防止强制熊猫数据帧的主要内容,如果未能解决你的问题,请参考以下文章

在不使用索引的情况下防止插入重复项

数据库表中不建索引,在插入数据时,通过sql语句防止重复添加

使用数据透视表(熊猫)中的小计行时保留索引部分(不同的列)

查找熊猫索引数据帧的最小值和最大值

从 JSONArray 插入/更新 +10000 行到 SqLite 时如何防止应用程序崩溃

熊猫在合并时强制到数据帧中的后缀