Как эффективно обновить поле в кадре данных на основе логических операторов для 4 других полей в записи? - PullRequest
2 голосов
/ 12 апреля 2019

Я анализирую и суммирую набор данных (" Report ") в виде фрейма данных Python для панд. В таблице указан результат процесса сопоставления между 4 различными наборами данных (« Inputs »), которые должны совпадать на одном и том же ключе.

В Отчете есть поле для каждого из Входов со счетчиком количества совпадений (> = 0) с базовым набором данных. Я хочу обновить поле в отчете, чтобы указать, сколько наборов данных соответствуют базовым данным (" matchCounter "), поэтому для любого числа успешных совпадений (т. Е.> 0) matchCounter должно увеличиваться с 1, максимум 4 (т. Е. Все четыре набора данных соответствуют базовым данным).

Я разработал этот процесс в ноутбуках Jupyter на небольшом наборе данных, содержащем около 100 000 записей, и, хотя мне удалось обновить поля matchCounter , я подозреваю, что это займет больше времени, чем следовало бы. Полный набор данных составляет 10 000 000 записей, и, согласно моим грубым вычислениям, для завершения моего текущего кода потребуется более 8 часов (на мой взгляд, это довольно простая операция).

Я немного ознакомился с повышением производительности фрейма данных ( производительность Pandas DataFrame ), но, поскольку я последовательно перебираю строки, а операторы if проверяются на элементах в строка, а не датафрейм, я не знаю, применимо ли это.

Вот краткая версия кода. Первый цикл for вызывает узкое место:

import numpy as np
import pandas as pd

df = pd.read_csv(fileIn, header=0)

df['match_count']= 0
df['exclude']= False

# This for loop takes 300+ seconds to execute 100'000 times     
for index, row in df.iterrows():
    matchCounter = 0
    if row['in_deeds'] > 0:
        matchCounter += 1
    if row['in_valuation'] > 0:
        matchCounter += 1
    if row['in_property'] > 0:
        matchCounter += 1
    if row['in_sg'] > 0:
        matchCounter += 1
    df.loc[index,'match_count'] = matchCounter

# This for loop takes only 11.75 seconds
i=0
for index, row in df.iterrows():
    if "EXCL" in row['stat_deeds'].upper():
        i=i+1
        df.loc[index,'exclude']=True
    elif "EXCL" in row['stat_valuation'].upper():
        i=i+1
        df.loc[index,'exclude']=True
    elif "EXCL" in row['stat_property'].upper():
        i=i+1
        df.loc[index,'exclude']=True
    elif "EXCL" in row['stat_sg'].upper():
        i=i+1
        df.loc[index,'exclude']=True

df = df.query('exclude == False')

Я впервые работаю с Пандами, и я также очень начинающий в Python, поэтому я предполагаю, что совершаю глупую ошибку. Но я также не уверен, неверны ли мои ожидания, и что это именно та производительность, которую я должен ожидать. Есть ли способ лучше? Даже если бы кто-то мог просто указать мне правильное направление, я был бы признателен за это!

Ответы [ 2 ]

1 голос
/ 12 апреля 2019

В прошлом у меня были похожие проблемы с итерацией по фреймам данных - df.iterrows() на первый взгляд кажется правильным выбором из-за простоты использования, но удобство приходит за цену. Вот полезный блог , в котором описываются методы в пандах для более эффективной итерации.

Результат - не используйте iterrows. В целом, доступ к строкам кадра данных можно получить, используя индекс в качестве итератора, а затем используя df.loc или df.iloc, например:

for i in df.index:
  print(df.loc[i, :])

Использование df.apply

Метод apply позволяет применять пользовательскую функцию ко всем столбцам или строкам кадра данных. Хотя использование здесь может быть несколько не интуитивным, оно является самым быстрым:

import numpy as np
import pandas as pd

def counter(row):

    if np.any(row[row > 0]):
        return np.sum(row[row > 0])
    else:
        return 0

N = 100000

df = pd.DataFrame({'A': np.random.randint(0, 2, N),
                   'B': np.random.randint(0, 2, N),
                   'C': np.random.randint(0, 2, N),
                   'D': np.random.randint(0, 2, N)})

df['match-count'] = df.apply(counter, axis=1, raw=True)

Здесь функция будет проверять каждую строку кадра данных (указывается axis=1); np.any возвращает True, если логическое выделение row[row > 0] не пусто, в этот момент логическое выделение уменьшается на np.sum, чтобы получить окончательный счет. Мы используем ключевое слово raw как True, так что передается необработанный массив numpy, который следует использовать в операциях сокращения (например, sum) для повышения производительности (см. docs ).

Это займет около 1,2 секунды для запуска на моем компьютере.

Редактировать

Ответ Джио показывает принцип, который я считаю хорошей практикой при использовании панд - если существуют методы (например, sum, cumsum), которые могут работать непосредственно с фреймами данных, попытайтесь использовать их, поскольку они всегда будут быстрее.

Там, где подобные методы не существуют, df.apply может быть полезным, если указать более сложные операции, которые нужно применить - просто совет на будущее!

Редактировать II

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

def counter(row):

    selection = row[['in_deeds', 'in_valuation', 'in_property', 'in_sg']] > 0

    if np.any(selection):
        return np.sum(selection)
    else:
        return 0
1 голос
/ 12 апреля 2019

Обновление после комментария OP:

df['match_count']=(df[['in_deeds','in_valuation','in_property','in_sg']]>0).astype(int).sum(axis=1)

Следующее также предоставит общее количество совпадений в каждой точке (каждой строке), взяв кумулятивную сумму количества совпадений.

df['match_count']=(df[['in_deeds','in_valuation','in_property','in_sg']]>0).astype(int).sum(axis=1).cumsum()

По частям :

Сначала мы проверяем (для каждой строки), является ли значение в указанных столбцах больше нуля. Это возвращает логическое значение True или False, которое мы преобразуем в целое число .astype(int)

df[['in_deeds','in_valuation','in_property','in_sg']]>0).astype(int)

Затем мы суммируем эти значения для каждой строки .sum(axis=1).
Это вернет один столбец, где в каждой строке мы знаем, сколько условий (>0) были выполнены.

Наконец, мы берем суммарную сумму по строкам, чтобы получить общее количество (в каждой строке) совпадений.

Наконец, мы создаем новый столбец df['match_count']= в исходном фрейме данных df и присваиваем результат этому столбцу.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...