2025, Oct 19 00:16

Как в Pandas автоматически приводить столбцы Decimal к float64 без хардкода

Как в Pandas DataFrame автоматически выявлять столбцы с decimal.Decimal и приводить их к float64 без хардкода: пороги обнаружения и практические советы.

При работе с финансовыми или измерительными данными нередко приходится иметь дело с decimal.Decimal. Как только такие значения попадают в Pandas DataFrame, соответствующие столбцы обычно получают тип object — не лучший вариант, если вы рассчитываете на «числовое» поведение из коробки. Задача — автоматически приводить все столбцы на базе Decimal к float64 уже на этапе создания, не прописывая названия столбцов вручную.

Подготовка

Рассмотрим словарь, в котором есть список значений decimal.Decimal. После создания DataFrame соответствующий столбец получает тип object, а не float64:

import pandas as pd
from decimal import Decimal

payload = {
    'Item': ['Apple', 'Banana', 'Orange'],
    'Price': [Decimal('1.25'), Decimal('0.75'), Decimal('2.00')],
    'Quantity': [10, 20, 15]
}

frame = pd.DataFrame(payload)
print(frame.dtypes)

# Вывод:
# Item        object
# Price       object
# Quantity     int64
# dtype: object

Даже pd.DataFrame.from_records(..., coerce_float=True) не изменит лежащие в основе значения Decimal. И хотя .astype(float) справляется, когда столбец известен, это не спасает, если заранее неизвестны имена столбцов.

Почему Decimal превращается в object

Когда столбец содержит экземпляры Decimal, Pandas выводит обобщённый тип object. Так сохраняются исходные объекты Python, но теряется «родное» числовое поведение. Решение — определить, какие столбцы действительно содержат значения Decimal, и за один проход конвертировать их в float64.

Рабочий шаблон преобразования

Практичный подход — выводить нужные столбцы из самих данных, собрать отображение и передать его в .astype. Если первой строке можно доверять как репрезентативной, используйте её для обнаружения столбцов с Decimal и их приведения:

from decimal import Decimal

probe = frame.iloc[0].map(type).eq(Decimal)
cast_map = dict.fromkeys(frame.columns[probe], float)
# Пример: {'Price': float}
converted = frame.astype(cast_map)

Так логика остаётся основанной на данных и избегает жёсткого перечисления имён столбцов.

Выбор порога выявления

Если опираться только на первую строку слишком рискованно, задайте порог по всему столбцу. В зависимости от требований можно конвертировать столбец, когда все значения — Decimal, когда встречается хотя бы одно значение Decimal, или когда доля значений Decimal превышает 90%:

# Конвертировать столбец, если все значения — Decimal
cast_map = dict.fromkeys(frame.columns[frame.map(type).eq(Decimal).all()], float)

# Конвертировать, если есть хотя бы одно значение Decimal
cast_map = dict.fromkeys(frame.columns[frame.map(type).eq(Decimal).any()], float)

# Конвертировать, если более 90% значений — Decimal
cast_map = dict.fromkeys(
    frame.columns[frame.map(type).eq(Decimal).mean().gt(0.9)],
    float
)
converted = frame.astype(cast_map)

После преобразования соответствующие столбцы ведут себя как настоящие числовые. Пример результирующих dtypes выглядит так:

Item        string[python]
Price              float64
Quantity             Int64
dtype: object

Почему это важно

Аккуратная работа с типами в DataFrame критична для предсказуемых численных операций, агрегирований и совместимости с другими библиотеками. Если оставлять насыщенные Decimal столбцы в виде object, легко нарваться на скрытые проблемы — от просадок по производительности до неожиданного поведения в последующих вычислениях. Декларативное приведение на этапе создания обеспечивает единообразные числовые типы без поддержки жёсткого списка имён столбцов.

Итоги

Если во входных данных встречается decimal.Decimal, доверьтесь самим данным: определите, какие столбцы нужно приводить. Найдите столбцы со значениями Decimal, постройте отображение к float и примените .astype. Будь то доверие к первой строке или порог по всему столбцу — принцип один: без хардкода, декларативно и с гарантией, что числовые столбцы ведут себя как числовые.

Статья основана на вопросе с StackOverflow от Gino и ответе от mozway.