Странное поведение панд DataFrame.sum, когда столбец содержит строковое значение - PullRequest
0 голосов
/ 26 октября 2018

У меня есть 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

Однако, когда я беру суммы по строкам, df3 дает другой результат по сравнению с df1 и df2.

Кроме того, кажется, что при оси = 0 сумма столбца, содержащего строки, вычисляться не будет, тогда как при оси = 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. Что вызывает различное поведение между sumcol1 и sumrow1?

  2. Что вызывает различное поведение между sumrow1 и sumrow3?

  3. Есть ли правильный способ получить результат, который совпадает с sumrow1 с df3?

Добавлено:

  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, панды 0.23.4.

Ответы [ 2 ]

0 голосов
/ 27 октября 2018

По вашему вопросу и диагнозу jpp, кадры данных выглядели одинаковыми, но они отличались по типу d в столбце 3.

Вот несколько методов сравнения, которые выявляют разницу:

>>> 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
0 голосов
/ 26 октября 2018

Есть несколько проблем:

  • Основная проблема заключается в том, что ваша конструкция df3 имеет все три серии с dtype object, тогда как df1 иdf2 имеют dtype=int для первых двух рядов.
  • Данные в кадрах данных Pandas организованы и хранятся по сериям [столбец].Поэтому приведение типов выполняется по серии .Следовательно, логика суммирования по «строкам и столбцам» обязательно отличается и не обязательно согласуется со смешанными типами.

Чтобы понять, что происходит с первым выпуском, вы должны понимать, что Панды не 't постоянно проверять , наиболее подходящий 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: нигде не задокументировано, что непрерывная серия проверок типа может или должна применяться после каждой операции.

Чтобы игнорировать нечисловые значения при суммировании по строкам или столбцам, вы можете принудительно преобразовать через pd.to_numeric с errors='coerce':

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
...