Как правильно обработать деление на ноль в pandas?
У меня есть датафрейм, содержащий различные данные. Мне просто необходимо разделить данные из столбца 'A' на данные из столбца 'B' и записать получившееся значение в столбец 'C'. В одной строке оба столбца 'A' и 'B' имеют значение 0. В строке, где значение 'B' равно 0, нужно записать в столбец 'C' 0. Я использую следующий код:
cond = (df["B"] != 0)
df['C'] = where(cond, (df["A"] / df["B"]).round(1), 0)
Но он всё равно выдаёт ошибку деления на ноль. Не могу понять в чём проблема
Ответы (5 шт):
во-первых, вам нужно вызывать where как метод датафрейма (df.where). во-вторых, в таком виде он вам не подойдёт, потому как работает со всеми элементами фрейма. проще и нагляднее будет сделать apply с указанием нужной оси. как-то так:
df["C"] = df.apply(lambda x: 0 if x["B"]==0 else x["A"]/x["B"], axis=1)
Проблема в том, что numpy.where хоть и выглядит как тернарный оператор, всё равно сначала вычисляет оба аргумента, даже если потом один из них не будет использован. То есть деление df["A"] / df["B"] всё равно происходит до фильтрации, и если там есть деление на ноль — будет ошибка.
Тебе лучше использовать pandas.Series.where(), потому что он работает "лениво", то есть выполняет операцию только там, где условие True.
Вот так будет правильно:
df['C'] = (df["A"] / df["B"]).round(1).where(df["B"] != 0, 0)
Этот способ делит только там, где B не ноль, а во всех остальных строках ставит 0, и не вызывает ошибку.
не понятно из-за чего тут ошибка, where что из numpy, что из pandas справляются с этим и выводят 0 в ответ
import pandas as pd
from pandas.core.computation.expressions import where
import numpy as np
df = pd.DataFrame({'A':[1, 2, 3, 0, 5], 'B':[3, 0, 1, 2, 4]})
df['C'] = where(df["B"] != 0, (df["A"] / df["B"]).round(1), 0)
df['D'] = (df["A"] / df["B"]).round(1).where(df["B"] != 0, 0)
df['E'] = np.where(df['B'] != 0, (df['A'] / df['B']).round(1), 0)
Для столбца С и Е используется функция np.where или pd.where, и деление выполняется только там, где B != 0
Для столбца D деление (df["A"] / df["B"]) выполняется всегда , даже если B == 0, и лишь потом заменяются значения, где условие не выполнено.
результат:
Скорее всего тип ваших данных - object. Если бы ваши данные были числовых типов int или float, то ошибка бы не возникла, а результатом деления на ноль было бы float('inf'). При этом, результатом деления 0 на 0 стало бы NaN. Поэтому, можно действовать так.
- Во-первых, привести типы колонок к
float. - Во-вторых, поделить одно на другое, не опасаясь ошибок.
- Ну и в третьих заменить
infиnanна0.
import numpy as np
import pandas as pd
# это для примера сгенерируем датафрейм
a = np.random.randint(0,10,1000)
b = np.random.randint(0,10,1000)
df = pd.DataFrame({'A':a, 'B':b})
# приведем датафрейм к типу float
df = df.astype(float)
# поделим колонки
df["C"] = df.A / df.B
# заменим бесконечности и пустоты
df.C = df.C.replace([np.inf, np.nan], 0)
Или можно все запихать в одно выражение, так будет даже побыстрее:
df['C'] = (df.A.astype(float) / df.B.astype(float)).replace([np.inf, np.nan], 0)
numpy 1.25.2 pandas 2.2.1
Библиотека pandas опирается на numpy, поведение которого при возникновении ошибок зависит от типа данных. Ошибки при работе со встроенными типами данных регулируются функцией seterr. Ошибки при работе с внешними типами данных генерируются и обрабатываются непосредственно объектами, помещенными в массив ndarray.
Ранее был дан ответ о поведении numpy.ndarray с данными типа 'object' (внешние относительно NumPy типы данных). Дополню его примером:
import numpy as np
try:
np.array(0, dtype='object') / 0
except Exception as e:
print('When 0/0 got', repr(e))
# When 0/0 got ZeroDivisionError('division by zero')
try:
np.array(1, dtype='object') / 0
except Exception as e:
print('When 1/0 got', repr(e))
# When 1/0 got ZeroDivisionError('division by zero')
class MyFloat(float):
def __truediv__(self, other):
if other == 0:
return 0
return super().__truediv__(other)
result = np.array(list(map(MyFloat, [0, 1, 2])), dtype='object') / [0, 0, 4]
print(repr(result))
# array([0, 0, 0.5], dtype=object)
В этом примере ошибку ZeroDivisionError возвращает не NumPy, а числовые объекты, определенные в Python. Мы можем изменить их поведение, как это показано на примере MyFloat.
Если же в массив помещаются данные встроенного типа, то обработка ошибок производится внутри библиотеки:
import numpy as np
print(np.geterr())
# {'divide': 'warn', 'over': 'warn', 'under': 'ignore', 'invalid': 'warn'}
original_errsetting = np.seterr(divide='raise', invalid='raise')
try:
np.array(0) / 0
except Exception as e:
print('When 0/0 got', repr(e))
# When 0/0 got FloatingPointError('invalid value encountered in divide')
try:
np.array(1) / 0
except Exception as e:
print('When 1/0 got', repr(e))
# When 1/0 got FloatingPointError('divide by zero encountered in divide')
np.seterr(**original_errsetting) # restore original error settings
Соответственно, ответ на ваш вопрос зависит от того, какую именно ошибку возвращает ваш код. Если это ZeroDivisionError, то проверьте тип ваших данных: print(df.dtypes). Если это 'object', попробуйте переопределить:
df[['A', 'B']] = df[['A', 'B']].astype(float)
Если же ваш код возвращает FloatingPointError, то нужно изменить настройки обработки ошибок внутри NumPy, например, установив значение 'warn' или 'ignore' для параметров division, работающего с ошибками вида 1/0 (бесконечность), и invalid, работающего с ошибками 0/0 (nan):
np.seterr(division='warn', invalid='warn')
P.S. Также всегда есть возможность изменить данные адресно:
import pandas as pd
df = pd.DataFrame({'A': [0, 1, 2], 'B': [0, 0, 1]}, dtype='object')
df['C'] = (df['A'] / df.loc[df['B'] != 0, 'B']).astype(float).fillna(0)
print(df)
print(df.dtypes)
# A B C
# 0 0 0 0.0
# 1 1 0 0.0
# 2 2 1 2.0
# A object
# B object
# C float64
# dtype: object
