В предыдущем посте о регрессионном проекте по продажам спиртных напитков в Айове я упомянул, что это был мой первый раз, когда я работал с данными, достаточно большими, чтобы беспокоиться о написании кода для оптимизации скорости. В этой статье я рассмотрю относительно простой пример этого процесса.

Вот данные, с которыми мы работаем. Обратите внимание на столбец state_bottle_retail. Каждая запись начинается со знака доллара, и чтобы сделать значения числовыми, мне нужно удалить эти знаки доллара.

В Python есть несколько способов сделать это. Но из-за размера этого набора данных оптимизация становится важной. Я продемонстрирую некоторые способы и сообщу, сколько времени они потребовали. Одно замечание: я буду проводить эти тесты на небольшом подмножестве примерно 10% от всего набора данных. Таким образом, даже несмотря на то, что все скорости очень высокие, а самая низкая составляет чуть более 130 миллисекунд, когда масштаб станет больше, это будет иметь большее значение. Это также служит демонстрацией важности и практики оптимизации. Это относительно упрощенный пример, но в определенных ситуациях подобные методы могут сэкономить часы или даже дни.

Для этих тестов я буду использовать магию %timeit ячейки в Jupyter Notebooks. Это удобный инструмент, который запускает несколько циклов операции и сообщает о наилучшем времени выполнения. Для этого вы просто набираете %timeit в начале строки с вашей операцией, запускаете ячейку и видите результаты.

Сначала я покажу вам фотографии всех проведенных мною тестов, а затем мы рассмотрим их один за другим.

Сначала я применил метод str.replace('$','') для всего столбца. Это наиболее простой способ, так как он просто заменяет «$» пробелом для каждого элемента в столбце. Как видите, это был самый медленный вариант, но он все еще относительно быстрый, как я упоминал выше. Это означает, что это займет около секунды для полного набора данных, содержащего более 2 миллионов строк. Это быстро. Но эта статья о том, чтобы стать быстрее.

Следующий метод использует метод pandas применить, который оптимизирован для выполнения операций над столбцом pandas. Метод apply требует, чтобы функция запускалась для каждого значения в столбце, поэтому я написал лямбда-функцию, выполняющую ту же функцию. В этом посте я подробнее расскажу об использовании метода apply с лямбда-функциями. В этом примере это выглядит так:

%timeit df.state_bottle_retail.apply(lambda x: x.replace('$',''))

Метод .apply работал точно так же, как и предполагалось, и ускорил операцию до 117 мс. Выглядит хорошо.

На следующем этапе я изменил метод .replace на метод .strip. Выполняется на одну операцию меньше. Вместо того, чтобы заменить «$» пробелом, он просто удаляет «$». Это выглядит так:

%timeit df.state_bottle_retail.apply(lambda x: x.strip('$'))

Это ускорило его до чуть менее 100 мс для всего столбца. Становиться лучше!

Затем был составлен список. Составление списков - очень эффективный метод перебора множества объектов в Python. Поэтому я попробовал тот же метод .strip с пониманием списка вместо метода .apply. Это выглядит так:

%timeit [x.strip('$') for x in df.state_bottle_retail]

Понимание списка подскочило до 72,3 мс. Отлично!

Наконец, я попробовал другой способ. Вместо использования функции для извлечения символа «$» я использовал Python, встроенный в [] нарезку. Его часто используют для нарезки и выбора нужных значений из списка, но он также может нарезать строки. Таким образом, [1:] разрезает каждую строку от второго значения до конца. Поскольку Python имеет нулевой индекс, что означает, что он начинает отсчет с 0, число 1 является вторым значением. «:» Указывает на то, что нужно разрезать до конца строки. Вот окончательное понимание списка с использованием метода нарезки строк:

%timeit [x[1:] for x in df.state_bottle_retail]

Это составляет 31,4 мс, что является не только самым быстрым временем, но и самым большим увеличением скорости для любого из этих тестов. Этот метод, безусловно, самый быстрый, но есть одно предостережение. Если перед каким-либо из значений нет символа «$», это приведет к удалению первого числа в этой строке. Так что будьте осторожны при использовании этого метода. Я лично предпочел бы использовать четвертый метод, понимание списка с помощью метода .strip. Хотя он не самый быстрый, но менее рискованный. Вам придется принять решение в зависимости от размера ваших данных и вашей уверенности в их целостности.

Одно небольшое замечание: чтобы любое из этих изменений действительно работало, вам нужно будет присвоить измененные значения обратно столбцу, который вы изменяете в своем DataFrame. Это выглядело бы так:

df.state_bottle_retail = [x.strip('$') for x in df.state_bottle_retail]

Оптимизация скорости вашего кода - увлекательный и интересный процесс. Это не всегда необходимо, но неплохо было бы привыкнуть к такому мышлению, особенно если вы хотите работать с большими данными или развертывать код для клиентов. Повеселись!

Обновление: « nzdatascientist» прокомментировал ниже другой метод. Я попробовал на тех же данных, и он молниеносно. Он намного превосходит другие методы без опасности удаления других значений, если в записи нет символа «$». Вот:

np.fromstring(df.state_bottle_retail.values.astype('|S7').tobytes().replace(b'$',b''), dtype='|S6')

Его тактовая частота составляет 14,3 мс, что более чем в два раза быстрее, чем у рискованного метода нарезки строк, и почти в 10 раз быстрее, чем у самого медленного из продемонстрированных методов. Похоже, .fromstring метод numpy оптимизирован для этого типа процессов. Кроме того, преобразование в байты и их замена также ускоряют процесс. Спасибо "nzdatascientist"!