7.37K subscribers
1.73K photos
73 videos
1 file
1.31K links
Привет! Мы — образовательная платформа в сфере аналитики Simulative: simulative.ru

Создаём курсы-симуляторы, где обучаем не на «апельсинках», а на кейсах из реального бизнеса.

Наш уютный чат: @itresume_chat
Поддержка: @simulative_support
Download Telegram
🔥 Cекреты функции sorted() в Python

Итак, в Python существует функция sorted(), которая используется для сортировки данныx. sorted() упорядочивает данные таким образом, чтобы было легче провести анализ и извлечь нужную информацию. Например, в аналитике, сортировка клиентов по возрасту, территориальной принадлежности и другим критериям - далеко не редкая задача.

Для сортировки списка объектов по одному из их атрибутов, как раз можем использовать функцию sorted() в Python. Она отсортирует любой итерируемый объект: список, кортеж или множество.

🟧 Например, у нас есть список клиентов с разными атрибутами, у каждого - имя, возраст и доход:

client_list = [
{'name': 'John', 'age': 25, 'income': 50000},
{'name': 'Mary', 'age': 30, 'income': 70000},
{'name': 'Bob', 'age': 35, 'income': 40000},
{'name': 'Alice', 'age': 27, 'income': 60000}
]

И мы хотим отсортировать этот список по возрастанию возраста. Мы не получим нужный результат если просто передадим наш список в sorted(). Нужно дополнительно указать ключ сортировки.

>> Ключ сортировки - это функция, которая принимает каждый элемент массива и возвращает значение, по которому будет производиться сортировка.

🟨 В нашем случае мы будем использовать лямбда-функцию, которая возвращает значение атрибута 'age' или 'income' каждого клиента в списке. Мы напишем key=lambda x: x['age'], чтобы указать, что мы хотим отсортировать список по возрастанию возраста клиентов. Если понадобится отсортировать список по другому атрибуту, то мы поменяем ключ на key=lambda x: x['нужный атрибут'].

Итак, используем лямбда-функцию, чтобы указать ключ сортировки. Она вернет значение 'age' для каждого клиента в списке:

sorted(client_list, key=lambda x: x['age'])

🟦 Аналогично, если вы хотите отсортировать список клиентов по убыванию их доходов - добавьте параметр reverse=True:

sorted(client_list, key=lambda x: x['income'], reverse=True)

🟪 Абсолютно таким же образом с помощью sorted() мы сможем отсортировать список объектов класса по некоторому атрибуту. Нам просто понадобится передать в key нужный нам атрибут: key=lambda x: x.age.

Таким образом, функция sorted() сокращает время на написание дополнительного кода и этим облегчает работу.

#python
🔥18👍52
🔥 Апгрейдим Pandas: создаем условные столбцы без лишних циклов

Кто работал с большими объемами данных в Pandas, наверняка знает, что обработать столбцы по условию может оказаться довольно затратной операцией. Мы часто слышим: используйте векторизацию! А что это, где это найти? Объясняем.

> Векторизация - это способ обработки данных, при котором операции применяются не поочередно к каждому элементу массива, а сразу ко всему массиву. Многие методы pandas, в общем говоря, уже векторизованы и вам не нужно об этом задумываться, НО!

Каким методом вы бы воспользовались для фильтрации столбца по условию?

🔵 Самый распространенный способ

Очень часто для этого используют apply(), да и не только для фильтрации, а и для применения какой-либо функции к столбцу. Очень нужный и полезный метод, иногда незаменимый.

Тем не менее apply() - это не что иное, как усовершенствованный цикл. И из-за этого теряется вся суть векторизации.

💡 Давайте покажем вам как можно создать условный столбец более эффективно - векторизованным способом:

Для начала создадим DataFrame с 10 миллионами случайных чисел от 0 до 1:

import pandas as pd
import numpy as np

df = pd.DataFrame(np.random.random((10**7,1)).round(2), columns = ['col1'])

🔴 Сразу посмотрим как поведет себя apply(). Определим функцию, которая для каждого числа будет возвращать 'Class A', если число больше 0.5, и 'Class B' в противном случае:

def assign_class(num):
if num > 0.5:
return 'Class A'
return 'Class B'

Передадим её в apply() и замерим время:

%timeit a = df.col1.apply(assign_class)

# 1.96 s ± 302 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

И чем больше будут данные, тем более печальным будет время. ☹️

🟢 Более удобный способ

Выход из ситуации - np.where(). Он позволяет обрабатывать все гораздо быстрее, как раз являясь одним из многих инструментов векторизации:

%timeit a = np.where(df['col1']>0.5, 'Class A', 'Class B')

# 282 ms ± 14.8 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

Существенное преимущество налицо!

Базовый синтаксис: numpy.where(condition [, x, y]).

Condition - условие; если оно выполняется то будут выбраны элементы из x; в противном случае - если это false, будут взяты элементы из y.

🟣 Так что, если хотите обработать большие объемы данных в Pandas быстро и без лишних циклов, однозначно используйте np.where(). А мы, со своей стороны cделаем все возможное, чтобы с такими инструментами вас знакомить: в Симуляторе “Аналитик данных” в модуле Python мы 2 главы уделяем самым популярным библиотекам Python: Pandas и Numpy!

#python #pandas #numpy
🔥20👍5
🔥 Нестандартная ситуация со словарями

Мы знаем, что вы - любите Python, а еще больше любите необычные фишки. Но знали ли вы, что при работе со словарями могут возникать довольно не интуитивные ситуации?

Посмотрите, чему будет равен my_dict?

my_dict = {
1.0: 'One (float)',
1: 'One (int)',
True: 'One (bool)',
'1': 'One (string)'
}

# {1.0: 'One (bool)', '1': 'One (string)'}

Несмотря на добавление 4 отдельных ключей в словарь, можете ли вы объяснить, почему он оставляет только два из них?

Мы знаем, что ключи должны быть уникальными и неизменяемыми. В данном случае нет никаких сомнений в том, что 1.0, 1 и True имеют разные типы данных.

type(1.0), type(1), type(True)

# (float, int, bool)

Если посмотреть на их id, убедимся что и они различны:

id(1.0), id(1), id(True)

# (139644507527152, 139645051222320, 9758592)

Но дело не только в этом! Ключи должны быть еще хешируемы. Для поиска ключа, Python использует именно его хэш-значение, а не id. Значит посмотрим на их хэш-значения:

hash(1.0), hash(1), hash(True)

# (1, 1, 1)

Вот и получается, поскольку они имеют одно и то же хэш-значение, словарь рассматривает их как одни и те же ключи.

📎 Но обратите еще внимание на итоговые ключ и значение: ключ - 1.0, в то время как значение соответствует ключу True.

Как так получилось?

Дело в том, что сначала 1.0 добавляется в качестве ключа, а значение устанавливается 'One (float)'. Затем, добавляя ключ 1, python распознает его как эквивалентное хэш-значение. И значение, соответствующее 1.0, перезаписывается на 'One (int)', в то время как ключ (1.0) сохраняется как есть.

Наконец, при добавлении True снова получаем эквивалентность хэша с существующим ключом 1.0. И снова, значение, которое изначально было 'One (float)' и обновлено до 'One (int)' на предыдущем шаге, перезаписывается на 'One (bool)'.

А почему сохранен строковый ключ '1'?

Тут все просто: он имеет уникальное хеш-значение, отличное от других ключей.

💥 Надеемся, что этот пост дал вам чуть более полное понимание работы словарей. Приходите к нам на Симулятор "Аналитик данных", где мы с нуля обучаем SQL и Python и делимся полезными фишками!

#python
🔥20👍6
🔥 Возможно, вы не знали этого о циклах for в Python

Недавно поступил вопрос от наших студентов, которые недавно освоили for-loop в Python и принялись экспериментировать.

💡 Загадка

При использовании цикла for невозможно изменить переменную цикла и повлиять на итерацию:

for i in range(5):
print(i)
i = 10

#0
#1
#2
#3


Как видно, переменная i не меняется, хотя мы попытались ее переназначить внутри цикла.

🟡 На самом деле, когда мы используем for-loop в Python, никто, как правило, не имеет намерений изменить переменную цикла внутри него. Но можно догадаться, откуда растут ноги. 🙂 Возможно, так могли бы поступить те, кто уже знаком с другими языками программирования, например, С++ или Java.

Но в питоне for-loops не работают таким образом. Изменение переменной цикла не влияет на итерацию.

При каждой итерации цикла for Python извлекает следующий элемент из указанного итерируемого объекта (iterable) - например, range(5). Затем значение этого элемента присваивается переменной цикла - например, i.

То есть после выполнения range(5) или любого другого range(n), объект, созданный range, становится абсолютно независящим от изменений на протяжении итераций. Будто мы написали цикл: for i in [0, 1, 2, 3, 4].

🖋 А теперь комментарий от наших преподавателей

Если вам действительно необходимо переназначить переменную цикла, используйте while, о котором частенько забывают, как только знакомятся с for-loop:

i=0
while i < 5:
print(i)
i = 10

#0

Или:

i=0
while i < 5:
print(i)
i += 2

#0
#2
#4

Это довольно простая вещь, но о ней стоит помнить при разработке!

👉 Записывайтесь на наш бесплатный курс по Python, где вы с нуля освоите новый язык программирования!

#python
👍14🔥9
🔥 Тонкости функции head в Pandas

Сегодня мы поделимся любопытной деталью из мира аналитики данных и нашей практики. Если вы работаете с Pandas, то наверняка знакомы с методом head(). Он позволяет вывести первые n рядов вашего датафрейма.

🟡 Но у head() есть нюанс. Если данные содержат повторяющиеся значения, head() просто вернет первые строки и не станет учитывать их. Показываем пример:

import pandas as pd

df = pd.DataFrame(
[['Игорь', 95],
['Яна', 100],
['Петр', 97],
['Иван', 95]],
columns = ['Имя', 'Оценка'])

df.sort_values('Оценка', ascending=False).head(3)

| | Имя | Оценка |
|---|-------|--------|
| 1 | Яна | 100 |
| 2 | Петр | 97 |
| 0 | Игорь | 95 |

В данном случае мы отсортировали датафрейм по столбцу 'Оценка' и просим вывести три первых позиции. Но если обратить внимание на значения оценок, то заметите, что у двух людей они равны. И если эти данные для вас важны, то использование head() может быть ошибкой.

🖋 Такие моменты часто имеют значение, например, при анализе транзакций, чтобы получить список n продуктов с самыми высокими продажами. Или мы, анализируя активность на itresume.ru при отборе страниц с наибольшим количеством посещений, не можем ограничиться head() - нам важны все значения (даже повторяющиеся).

🟢 И чтобы избежать этого, мы используем метод nlargest() вместо head(). Он позволяет извлекать верхние k строк отсортированного датафрейма, учитывая повторяющиеся значения. Давайте рассмотрим пример:

df.nlargest(n=3,
columns='Оценка',
keep='all')

| | Имя | Оценка |
|---|-------|--------|
| 1 | Яна | 100 |
| 2 | Петр | 97 |
| 0 | Игорь | 95 |
| 3 | Иван | 95 |

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

‼️ В нашем бесплатном курсе по Python рассказываем, как работать с модулями и библиотеками Python: как устанавливать, импортировать и какие могут быть подводные камни ☺️

#python #pandas
🔥22👍13
🔥 Создание поля объектов в Python с помощью setattr()

В работе с Python порой возникают ситуации, когда нужно динамически создавать поля объектов. Например, вы можете столкнуться с задачей занесения данных из словаря в атрибуты класса (мы столкнулись с такой задачей 🙂).

Код, который вы напишете для этой задачи, должен уметь обрабатывать различные словари со своим набором ключей и значениями. Неординарная задача. Но мы знаем, как это сделать!

🟢 Один из способов решения этой задачи - использовать функцию setattr(). Эта функция позволяет динамически назначать поля объекта во время выполнения программы. Она принимает три аргумента: объект, имя атрибута и значение атрибута.

Например, предположим, что у нас есть словарь my_dict:

my_dict = {
    'name': 'Максим',
    'age': 25,
    'city': 'Москва'
}

Мы можем создать объект Person и использовать функцию setattr() для присвоения значений атрибутам:

class Person:
    pass

p = Person()
for k, v in my_dict.items():
     setattr(p, k, v)

В результате мы получим объект Person с атрибутами name, age и city.

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

💡 Кроме того, функция setattr() может пригодиться в других ситуациях, когда нужно динамически создавать поля объекта. Например, при работе с API или базами данных, когда набор полей может меняться в зависимости от запроса пользователя.

‼️ Чтобы освоить или улучшить свои навыки программирования на Python, присоединяйтесь к нашему бесплатному курсу!

#python
🔥12👍7
🤨 Действительно ли параметр inplace в Pandas ускоряет операции?

Многие пользователи Pandas считают, что использование операций с параметром inplace=True ускорит работу с датафреймами. Согласны ли вы с этим? Всегда ли это работает?

🟢 Давайте это проверим

Inplace - это параметр методов Pandas, который позволяет изменять DataFrame без создания нового объекта. Звучит так, будто это должно работать быстрее, но есть некоторые нюансы:

- Inplace, вопреки тому, что следует из названия, часто не препятствует созданию копий. Это первый пункт против скорости подобного способа.
- SettingWithCopy - часто получаемое предупреждение - оно не влияет на выполнение кода. Однако же получается Pandas выполняет дополнительные проверки, чтобы убедиться, что датафрейм изменен корректно. Соответственно, второй пункт против скорости inplace.
- И последнее - inplace операции могут быть менее гибкими, так как не позволяют объединить несколько методов в одной строке.

Но это лишь предположения, давайте убедимся в них!

🔵 Мы провели небольшой анализ

Смотрите в карточке под постом как некоторые методы работают с inplace.

❗️Какие-то методы действительно работают быстрее, некоторые гораздо быстрее, но все-таки большинство - медленнее.

Поэтому, перед тем, как применять inplace, обязательно оцените время и потенциальную пользу в вашем конкретном случае и не потеряйте важные данные!

🧠 Если хотите все проверить самостоятельно - делимся ссылкой на коллаб.

#python #pandas
🔥5
🔥 Переменная цикла и области видимости

Не так давно у нас был пост о том, что переменную цикла нельзя изменить внутри for-loop. Давайте продолжим тему переменной цикла, но взглянем на нее под другим углом!

🤔 Посмотрите на блоки кода ниже и ответьте, что вы получите после выполнения?

a = 1
for a in range(6):
pass

print(a)

------------------------

a = 1
[... for a in range(6)]

print(a)

Итак, в первом случае получим 5, а во втором - 1. То есть цикл for обновил существующую переменную a, но генератор списка этого не сделал. Итак, вопрос:

Почему переменная цикла обрабатывается по-разному в for-loops и list cоmprehension?

🟢 В цикле for переменная цикла как бы «утекает» в окружающую область видимости. Другими словами, как только цикл завершится, вы все равно сможете получить доступ к переменной цикла. Убедимся:

for loop_var in range(6):
pass

print(loop_var)
# 5

Мы могли бы написать даже что-то такое:

b = 1
for a in range(6):
pass

print(a, b)
# 5 1

Мы можем получить доступ к переменной цикла вне него и убедиться, что переменная создается и изменяется автоматически.

🟠 Но генераторы списков так не работают. Вместо этого переменная цикла всегда остается локальной для генератора списка. Она никогда не утекает «наружу».

[... for loop_var2 in range(6)]

print(loop_var2)
# NameError: name 'loop_var2' is not defined

Вот почему существующая переменная a, которая также использовалась внутри генератора списка, осталась неизменной. Генератор списка определил переменную цикла (a), локальную для его области видимости.

#python
🔥10👍4🤩2
💥 3 лайфхака по считыванию csv-файла в Python 💥

Нам, аналитикам, часто приходится работать с csv-файлами. Все-таки это удобный способ хранения больших наборов данных и дальнейшей работы с ними.

И раз уж эта тема так близка и важна для нас всех, поделимся 3 лайфхаками, которых вы могли не знать при работе с csv.

🔵 Первый лайфхак

Иногда при больших исходных данных работать приходится только с некоторым количеством из них. И если заранее известно, что из условно 10 миллионов строк вам будет достаточно только 1000, задайте в read_csv параметр nrows. Это позволит сэкономить и память, и время.

В противном же случае пришлось бы выполнить сразу несколько действий: загрузить весь сет, взять от него срезом только первую 1000 строк, и удалить первоначальный. Один параметр вместо трех строк. Удобно!

#Было
df = pd.read_csv('file.csv')
new_df = df[:1000].copy()
del df
_______
#Стало
df = pd.read_csv('file.csv',
nrows = 1000)

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

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

#Было
df = pd.read_csv('file.csv')
df = df[['col1', 'col2',
'col3']]
________
#Стало
df = pd.read_csv('file.csv',
usecols = ['col1', 'col2',
'col3'])

🟢 Третий лайфхак, но не по важности - skiprows. Например, если файл содержит несколько строк заголовка или метаданные в начале файла, которые нет необходимости обрабатывать, можно легко их пропустить.

То есть если вы передадите список [0, 1, 2], то будут пропущены первые три строки:

df = pd.read_csv('file.csv', 
skiprows = [0, 1, 2])

💡 Более того, вы можете не просто пропустить несколько идущих подряд строк, вы можете передать в skiprows функцию, которая определит, какие строки необходимо сохранить, а какие можно пропустить. 

Например, известно, что в файле каждая вторая строка пустая, просто передайте в skiprows лямбда-функцию:

df = pd.read_csv('file.csv', 
skiprows=lambda x:
(x % 2) != 0))

В нашем 👉 Симуляторе «Аналитик данных» 👈 в модуле по Python эти уловки вам точно пригодятся, ведь вы будете работать с файлами (логами, выгрузками и таблицами) из реального бизнеса от наших партнеров!

#python #pandas
🔥17👍8
🔥 Избегайте этого при работе с Pandas

Как вы выбираете элементы при работе с датафреймами? Сначала строку, а потом столбец или столбец, а за ним строка? А может вы вовсе определяете порядок наугад? Если так, то вы упускаете огромный потенциал производительности! Скорее читайте наш пост.

🔵 При индексации датафрейма важно определить порядок выбора - сначала столбец или сначала строки, так как это существенно влияет на время выполнения операций.

Например, при выборе столбца и строки последовательно, такая операция выполняется более чем в 15 раз быстрее по сравнению со случаем, когда сначала выбирается строка, а затем столбец:

# сначала выбираем столбец, затем строку
%timeit df['col']['row']
#2.96 µs

# наоборот - строка, затем столбец
%timeit df.loc['row']['col']
#45.4 µs

Почему это происходит?

Pandas DataFrame - это структура данных, состоящая из столбцов, и последовательные элементы в каждом столбце хранятся рядом друг с другом в памяти.

И поскольку процессоры умеют эффективно работать со смежными блоками памяти, доступ к столбцу осуществляется намного быстрее, чем к строке.

# выбираем столбец (смежные блоки памяти) 
%timeit df['my_column']
# 1.73 µs

# выбираем строку (несмежные)
%timeit df.iloc[0]
# 38.4 µs

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

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

🟣 Напротив, если мы выбираем сначала столбец, то элементы извлекаются из смежных блоков памяти, что работает гораздо эффективнее. Кроме того, столбец по своей сути уже является серией Pandas, и нет никаких расходов на его преобразование.

Так что запоминайте: сначала столбец, а потом строка 💡


Присоединяйтесь к нашим студентам 👉 Симулятора «Аналитик данных» 👈, чтобы не просто уметь решать рабочие кейсы, а знать, как это делать оптимально!

#python #pandas
🔥22👍8