Pandas 使用字符串和浮点数字典的 DataFrame 分配错误?
Pandas DataFrame Assignment Bug using Dictionaries of Strings and Floats?
问题
Pandas 似乎支持使用 df.loc
将字典分配给行条目,如下所示:
df = pd.DataFrame(columns = ['a','b','c'])
entry = {'a':'test', 'b':1, 'c':float(2)}
df.loc[0] = entry
正如预期的那样,Pandas 将字典值插入到基于字典键的相应列中。打印出来:
a b c
0 test 1 2.0
但是,如果您覆盖相同的条目,Pandas 将分配字典 keys 而不是字典值。打印出来:
a b c
0 a b c
问题
为什么会这样?
具体来说,为什么 仅 发生在第二次作业中?所有后续赋值都恢复为原始结果,包含(几乎)预期值:
a b c
0 test 1 2
我说几乎是因为 c
上的 dtype
实际上是 object
而不是所有后续结果的 float
。
我确定只要涉及字符串和浮点数,就会发生这种情况。如果它只是一个字符串和整数,或者整数和浮点数,您将不会发现此行为。
示例代码
df = pd.DataFrame(columns = ['a','b','c'])
print(f'empty df:\n{df}\n\n')
entry = {'a':'test', 'b':1, 'c':float(2.3)}
print(f'dictionary to be entered:\n{entry}\n\n')
df.loc[0] = entry
print(f'df after entry:\n{df}\n\n')
df.loc[0] = entry
print(f'df after second entry:\n{df}\n\n')
df.loc[0] = entry
print(f'df after third entry:\n{df}\n\n')
df.loc[0] = entry
print(f'df after fourth entry:\n{df}\n\n')
这给出了以下打印输出:
empty df:
Empty DataFrame
Columns: [a, b, c]
Index: []
dictionary to be entered:
{'a': 'test', 'b': 1, 'c': float(2)}
df after entry:
a b c
0 test 1 2.0
df after second entry:
a b c
0 a b c
df after third entry:
a b c
0 test 1 2
df after fourth entry:
a b c
0 test 1 2
有趣的发现。在 pandas 版本 1.2.4
、all 上,后续数据帧具有值 a b c
,而不仅仅是第二个。
empty df:
Empty DataFrame
Columns: [a, b, c]
Index: []
dictionary to be entered:
{'a': 'test', 'b': 1, 'c': 2.3}
df after entry:
a b c
0 test 1 2.3
df after second entry:
a b c
0 a b c
df after third entry:
a b c
0 a b c
顺便说一句,它似乎只有在分配给 new 行时才能正常工作。所以它只是在那种情况下将键与列相关联。对于对现有行的所有后续重新分配,它在 1.2.4
.
中具有观察到的意外行为
df.loc[1] = entry
print(f'df after assigning to a new row:\n{df}\n\n')
# output:
df after assigning to a new row:
a b c
0 a b c
1 test 1 2.3
df.loc[1] = entry
print(f'df after reapting:\n{df}\n')
# output:
df after reapting:
a b c
0 a b c
1 a b c
现有行发生这种情况的原因(除了是一个错误)是它迭代集合。在字典的情况下,它是键。
在文档部分“Setting with enlargement”
The .loc/[]
operations can perform enlargement when setting a non-existent key for that axis.
In the Series
case this is effectively an appending operation.
所以对于新行,它是在“扩大”输入,但对于现有行,它是在输入上迭代(字典的键,而不是值)。
对于列表,它的工作方式与人们预期的一样。
df.loc[2] = list(entry.values())
print(f'df when assigning from a list\n{df}\n')
# output
df when assigning from a list
a b c
0 a b c
1 a b c
2 test 1 2.3
df.loc[2] = list(entry.values())
print(f'df when assigning from a list 2nd time\n{df}\n')
# output
df when assigning from a list 2nd time
a b c
0 a b c
1 a b c
2 test 1 2.3
(这就是基于文档的原因。我认为实际的技术原因可能只有在仔细阅读源代码后才会显而易见。)
恕我直言,它要么适用于所有 assignments/re-assignemnts,要么根本不允许。我同意这应该作为错误提出,因为 .
1.2.4行为如下:
empty df:
Empty DataFrame
Columns: [a, b, c]
Index: []
dictionary to be entered:
{'a': 'test', 'b': 1, 'c': 2.3}
df after entry:
a b c
0 test 1 2.3
df after second entry:
a b c
0 a b c
df after third entry:
a b c
0 a b c
df after fourth entry:
a b c
0 a b c
第一次df.loc[0]
函数是_setitem_with_indexer_missing
函数是运行因为轴上没有索引0
:
这一行是运行:
elif isinstance(value, dict):
value = Series(
value, index=self.obj.columns, name=indexer, dtype=object
)
这会将 dict
变成一个系列,并且它的行为符合预期。
然而,在未来的时间里,由于索引没有丢失(存在索引 0
),_setitem_with_indexer_split_path
是 运行:
elif len(ilocs) == len(value):
# We are setting multiple columns in a single row.
for loc, v in zip(ilocs, value):
self._setitem_single_column(loc, v, pi)
这只是用 dict
:
中的每个值压缩列位置
在这种情况下,这大致相当于:
entry = {'a': 'test', 'b': 1, 'c': float(2.3)}
print(list(zip([0, 1, 2], entry)))
# [(0, 'a'), (1, 'b'), (2, 'c')]
因此,为什么值是键。
因此,问题并不像看起来那么具体:
import pandas as pd
df = pd.DataFrame([[1, 2, 3]], columns=['a', 'b', 'c'])
print(f'df:\n{df}\n\n')
entry = {'a': 'test', 'b': 1, 'c': float(2.3)}
print(f'dictionary to be entered:\n{entry}\n\n')
df.loc[0] = entry
print(f'df after entry:\n{df}\n\n')
initial df:
a b c
0 1 2 3
dictionary to be entered:
{'a': 'test', 'b': 1, 'c': 2.3}
df after entry:
a b c
0 a b c
如果索引 loc 存在,它不会转换为系列:它只是将列 locs 与可迭代对象压缩在一起。在字典的情况下,这意味着键是包含在框架中的值。
这也可能是为什么 只有 迭代器 return 它们的 值 是可接受的左手参数的原因loc
作业。
我也同意 应该将其作为错误提出。
1.1.5行为如下:
初始分配与 1.2.4 相比没有变化,但是:
这里的数据类型值得注意:
import pandas as pd
df = pd.DataFrame({0: [1, 2, 3]}, columns=['a', 'b', 'c'])
entry = {'a': 'test', 'b': 1, 'c': float(2.3)}
# First Entry
df.loc[0] = entry
print(df.dtypes)
# a object
# b object
# c float64
# dtype: object
# Second Entry
df.loc[0] = entry
print(df.dtypes)
# a object
# b object
# c object
# dtype: object
# Third Entry
df.loc[0] = entry
print(df.dtypes)
# a object
# b object
# c object
# dtype: object
# Fourth Entry
df.loc[0] = entry
print(df.dtypes)
# a object
# b object
# c object
# dtype: object
它们引人注目的原因是当
take_split_path = self.obj._is_mixed_type
是真的。它执行与 1.2.4 中相同的 zip 操作。
然而,在 1.1.5 中,数据类型都是 object
,因此 take_split_path
仅在第一次赋值后为 false,因为 c
是 float64
。后续赋值使用:
if isinstance(value, (ABCSeries, dict)):
# TODO(EA): ExtensionBlock.setitem this causes issues with
# setting for extensionarrays that store dicts. Need to decide
# if it's worth supporting that.
value = self._align_series(indexer, Series(value))
这自然会正确对齐 dict
。
问题
Pandas 似乎支持使用 df.loc
将字典分配给行条目,如下所示:
df = pd.DataFrame(columns = ['a','b','c'])
entry = {'a':'test', 'b':1, 'c':float(2)}
df.loc[0] = entry
正如预期的那样,Pandas 将字典值插入到基于字典键的相应列中。打印出来:
a b c
0 test 1 2.0
但是,如果您覆盖相同的条目,Pandas 将分配字典 keys 而不是字典值。打印出来:
a b c
0 a b c
问题
为什么会这样?
具体来说,为什么 仅 发生在第二次作业中?所有后续赋值都恢复为原始结果,包含(几乎)预期值:
a b c
0 test 1 2
我说几乎是因为 c
上的 dtype
实际上是 object
而不是所有后续结果的 float
。
我确定只要涉及字符串和浮点数,就会发生这种情况。如果它只是一个字符串和整数,或者整数和浮点数,您将不会发现此行为。
示例代码
df = pd.DataFrame(columns = ['a','b','c'])
print(f'empty df:\n{df}\n\n')
entry = {'a':'test', 'b':1, 'c':float(2.3)}
print(f'dictionary to be entered:\n{entry}\n\n')
df.loc[0] = entry
print(f'df after entry:\n{df}\n\n')
df.loc[0] = entry
print(f'df after second entry:\n{df}\n\n')
df.loc[0] = entry
print(f'df after third entry:\n{df}\n\n')
df.loc[0] = entry
print(f'df after fourth entry:\n{df}\n\n')
这给出了以下打印输出:
empty df:
Empty DataFrame
Columns: [a, b, c]
Index: []
dictionary to be entered:
{'a': 'test', 'b': 1, 'c': float(2)}
df after entry:
a b c
0 test 1 2.0
df after second entry:
a b c
0 a b c
df after third entry:
a b c
0 test 1 2
df after fourth entry:
a b c
0 test 1 2
有趣的发现。在 pandas 版本 1.2.4
、all 上,后续数据帧具有值 a b c
,而不仅仅是第二个。
empty df:
Empty DataFrame
Columns: [a, b, c]
Index: []
dictionary to be entered:
{'a': 'test', 'b': 1, 'c': 2.3}
df after entry:
a b c
0 test 1 2.3
df after second entry:
a b c
0 a b c
df after third entry:
a b c
0 a b c
顺便说一句,它似乎只有在分配给 new 行时才能正常工作。所以它只是在那种情况下将键与列相关联。对于对现有行的所有后续重新分配,它在 1.2.4
.
df.loc[1] = entry
print(f'df after assigning to a new row:\n{df}\n\n')
# output:
df after assigning to a new row:
a b c
0 a b c
1 test 1 2.3
df.loc[1] = entry
print(f'df after reapting:\n{df}\n')
# output:
df after reapting:
a b c
0 a b c
1 a b c
现有行发生这种情况的原因(除了是一个错误)是它迭代集合。在字典的情况下,它是键。 在文档部分“Setting with enlargement”
The
.loc/[]
operations can perform enlargement when setting a non-existent key for that axis.In the
Series
case this is effectively an appending operation.
所以对于新行,它是在“扩大”输入,但对于现有行,它是在输入上迭代(字典的键,而不是值)。
对于列表,它的工作方式与人们预期的一样。
df.loc[2] = list(entry.values())
print(f'df when assigning from a list\n{df}\n')
# output
df when assigning from a list
a b c
0 a b c
1 a b c
2 test 1 2.3
df.loc[2] = list(entry.values())
print(f'df when assigning from a list 2nd time\n{df}\n')
# output
df when assigning from a list 2nd time
a b c
0 a b c
1 a b c
2 test 1 2.3
(这就是基于文档的原因。我认为实际的技术原因可能只有在仔细阅读源代码后才会显而易见。)
恕我直言,它要么适用于所有 assignments/re-assignemnts,要么根本不允许。我同意这应该作为错误提出,因为
1.2.4行为如下:
empty df:
Empty DataFrame
Columns: [a, b, c]
Index: []
dictionary to be entered:
{'a': 'test', 'b': 1, 'c': 2.3}
df after entry:
a b c
0 test 1 2.3
df after second entry:
a b c
0 a b c
df after third entry:
a b c
0 a b c
df after fourth entry:
a b c
0 a b c
第一次df.loc[0]
函数是_setitem_with_indexer_missing
函数是运行因为轴上没有索引0
:
这一行是运行:
elif isinstance(value, dict):
value = Series(
value, index=self.obj.columns, name=indexer, dtype=object
)
这会将 dict
变成一个系列,并且它的行为符合预期。
然而,在未来的时间里,由于索引没有丢失(存在索引 0
),_setitem_with_indexer_split_path
是 运行:
elif len(ilocs) == len(value):
# We are setting multiple columns in a single row.
for loc, v in zip(ilocs, value):
self._setitem_single_column(loc, v, pi)
这只是用 dict
:
在这种情况下,这大致相当于:
entry = {'a': 'test', 'b': 1, 'c': float(2.3)}
print(list(zip([0, 1, 2], entry)))
# [(0, 'a'), (1, 'b'), (2, 'c')]
因此,为什么值是键。
因此,问题并不像看起来那么具体:
import pandas as pd
df = pd.DataFrame([[1, 2, 3]], columns=['a', 'b', 'c'])
print(f'df:\n{df}\n\n')
entry = {'a': 'test', 'b': 1, 'c': float(2.3)}
print(f'dictionary to be entered:\n{entry}\n\n')
df.loc[0] = entry
print(f'df after entry:\n{df}\n\n')
initial df:
a b c
0 1 2 3
dictionary to be entered:
{'a': 'test', 'b': 1, 'c': 2.3}
df after entry:
a b c
0 a b c
如果索引 loc 存在,它不会转换为系列:它只是将列 locs 与可迭代对象压缩在一起。在字典的情况下,这意味着键是包含在框架中的值。
这也可能是为什么 只有 迭代器 return 它们的 值 是可接受的左手参数的原因loc
作业。
我也同意
1.1.5行为如下:
初始分配与 1.2.4 相比没有变化,但是:
这里的数据类型值得注意:
import pandas as pd
df = pd.DataFrame({0: [1, 2, 3]}, columns=['a', 'b', 'c'])
entry = {'a': 'test', 'b': 1, 'c': float(2.3)}
# First Entry
df.loc[0] = entry
print(df.dtypes)
# a object
# b object
# c float64
# dtype: object
# Second Entry
df.loc[0] = entry
print(df.dtypes)
# a object
# b object
# c object
# dtype: object
# Third Entry
df.loc[0] = entry
print(df.dtypes)
# a object
# b object
# c object
# dtype: object
# Fourth Entry
df.loc[0] = entry
print(df.dtypes)
# a object
# b object
# c object
# dtype: object
它们引人注目的原因是当
take_split_path = self.obj._is_mixed_type
是真的。它执行与 1.2.4 中相同的 zip 操作。
然而,在 1.1.5 中,数据类型都是 object
,因此 take_split_path
仅在第一次赋值后为 false,因为 c
是 float64
。后续赋值使用:
if isinstance(value, (ABCSeries, dict)):
# TODO(EA): ExtensionBlock.setitem this causes issues with
# setting for extensionarrays that store dicts. Need to decide
# if it's worth supporting that.
value = self._align_series(indexer, Series(value))
这自然会正确对齐 dict
。