当列包含字符串值时,熊猫数据帧.sum 的奇怪行为



我有 3 个调查回复的熊猫数据帧,它们看起来完全相同,但以不同的方式创建:

import pandas as pd
df1 = pd.DataFrame([[1,2,3],[4,5,'hey'],[7,8,9]])
df2 = pd.DataFrame([[1,2,3],[4,5,6],[7,8,9]])
df2.loc[1,2] = 'hey'
df3 = pd.DataFrame(index=range(3), columns=range(3))
for i in range(3):
for j in range(3):
if (i,j) != (1,2):
df3.loc[i,j] = i*3 + j + 1
else:
df3.loc[i,j] = 'hey'
# df1, df2, df3 look the same as below
0  1    2
0  1  2    3
1  4  5  hey
2  7  8    9

现在,当我沿着列取总和时,它们都给了我相同的结果。

sumcol1 = df1.sum()
sumcol2 = df2.sum()
sumcol3 = df3.sum()
# sumcol1, sumcol2, sumcol3 look the same as below
0    12
1    15
dtype: int64

但是,当我跨行求和时,与df1df2相比,df3给出了不同的结果。

此外,似乎当 axis=0 时,不会计算包含字符串的列的总和,而当 axis=1 时,将使用属于跳过字符串元素的列的元素计算所有行总和。

sumrow1 = df1.sum(axis=1)
sumrow2 = df2.sum(axis=1)
sumrow3 = df3.sum(axis=1)
#sumrow1
0     3
1     9
2    15
dtype: int64
#sumrow2
0     3
1     9
2    15
dtype: int64
#sumrow3
0    0.0
1    0.0
2    0.0
dtype: float64

我对此有3个问题。

  1. 是什么导致了sumcol1sumrow1之间的不同行为?

  2. 是什么导致了sumrow1sumrow3之间的不同行为?

  3. 有没有一种正确的方法可以获得与df3sumrow1相同的结果?

添加:

  1. 有没有一种聪明的方法可以在保留字符串的同时只添加数值?

    • 我目前的解决方法(感谢jpp的友好回答(:

      df = pd.DataFrame([[1,2,3],[4,5,'hey'],[7,8,9]])
      df_c = df.copy()
      for col in df.select_dtypes(['object']).columns:
      df_c[col] = pd.to_numeric(df_c[col], errors='coerce')
      df['sum'] = df_c.sum(axis=1)
      #result
      0  1    2   sum
      0  1  2    3   6.0
      1  4  5  hey   9.0
      2  7  8    9  24.0
      

我正在使用Python 3.6.6,pandas 0.23.4。

有几个问题:

  • 主要问题是你的构造df3拥有所有 三个系列与dtypeobject,而df1df2dtype=int前两个系列。
  • Pandas 数据帧中的数据按系列[列] 进行组织和存储。因此,类型转换是按系列执行的。因此,跨"行和列"求和的逻辑必然不同,并且不一定与混合类型一致。

要了解第一个问题发生了什么,你必须明白,Pandas 不会在每次操作后不断检查选择最合适的 dtype。这将非常昂贵。

您可以自行检查dtypes

print({'df1': df1.dtypes, 'df2': df2.dtypes, 'df3': df3.dtypes})
{'df1': 0     int64
1     int64
2    object
dtype: object,
'df2': 0     int64
1     int64
2    object
dtype: object,
'df3': 0    object
1    object
2    object
dtype: object}

您可以通过检查转换后是否有任何空值结果的操作有选择地将转换应用于df3

for col in df3.select_dtypes(['object']).columns:
col_num = pd.to_numeric(df3[col], errors='coerce')
if not col_num.isnull().any():  # check if any null values
df3[col] = col_num          # assign numeric series
print(df3.dtypes)
0     int64
1     int64
2    object
dtype: object

然后,您应该看到一致的治疗。在这一点上,值得丢弃您的原始df3:在每次操作后可以应该应用连续系列类型检查的任何地方都没有记录。

要在跨行或列求和时忽略非数字值,您可以通过带有errors='coerce'pd.to_numeric强制转换:

df = pd.DataFrame([[1,2,3],[4,5,'hey'],[7,8,9]])
col_sum = df.apply(pd.to_numeric, errors='coerce').sum()
row_sum = df.apply(pd.to_numeric, errors='coerce').sum(1)
print(col_sum)
0    12.0
1    15.0
2    12.0
dtype: float64
print(row_sum)
0     6.0
1     9.0
2    24.0
dtype: float64

根据您的问题和 jpp 的诊断,数据帧看起来相同,但它们在第 3 列的 dtype 上有所不同。

以下是一些揭示差异的比较方法:

>>> df1.equals(df3)
False # not so useful, doesn't tell you why they differ

您真正需要的是pandas.testing.assert_frame_equal

>>> import pandas.testing
>>> pandas.testing.assert_frame_equal(df1, df3)
AssertionError: Attributes are different
Attribute "dtype" are different
[left]:  int64
[right]: object

pandas.testing.assert_frame_equal()有以下有用的参数厨房水槽,您可以自定义所需的任何内容:

check_dtype : bool, default True    
Whether to check the DataFrame dtype is identical.
check_index_type : bool / string {‘equiv’}, default False    
Whether to check the Index class, dtype and inferred_type are identical.
check_column_type : bool / string {‘equiv’}, default False    
Whether to check the columns class, dtype and inferred_type are identical.
check_frame_type : bool, default False    
Whether to check the DataFrame class is identical.
check_less_precise : bool or int, default False    
Specify comparison precision. Only used when check_exact is False. 5 digits (False) or 3 digits (True) after decimal points are compared. If int, then specify the digits to compare
check_names : bool, default True    
Whether to check the Index names attribute.
by_blocks : bool, default False    
Specify how to compare internal data. If False, compare by columns. If True, compare by blocks.
check_exact : bool, default False    
Whether to compare number exactly.
check_datetimelike_compat : bool, default False    
Compare datetime-like which is comparable ignoring dtype.
check_categorical : bool, default True    
Whether to compare internal Categorical exactly.
check_like : bool, default False    
If true, ignore the order of rows & columns

最新更新