Занятие 8
Библиотека Pandas представляет собой библиотеку Python для обработки и анализа табличных данных. Название этой библиотеки происходит от «panel data» («панельные данные»).
Для установки pandas выполним в командной строке команду:
# Если pandas уже установлен, то можно ничего не делать.
# Иначе нужно убрать комментарий ниже.
#!pip install pandas
import pandas as pd
import numpy as np
В Pandas есть два класса объектов: Series (одномерный массив, в котором можно хранить значения любого типа данных) и DataFrame (двумерный массив (таблица), в котором столбцами являются объекты класса Series).
Создать Series можно следующим образом:
s = pd.Series(data, index=index)
здесь в качестве data может быть массив numpy, словарь или число, а в аргумент index передаётся список меток осей.
s = pd.Series(np.arange(5), index=["a", "b", "c", "d", "e"])
print(s)
print()
s = pd.Series(np.linspace(0, 1, 5))
print(s)
Если в качестве data задано число, то количество элементов в Series будет равно числу меток в index, а значения будут равны data:
index = ["a", "b", "c"]
print(pd.Series(5, index=index))
К Series применимы операции взятия элемента по индексу, срезы, поэлементные математические операции аналогично массивам numpy.
s = pd.Series(np.arange(5), index=["a", "b", "c", "d", "e"])
print('Исходный массив: ', s, sep='\n')
print('Взятие одного элемента: ', s["a"], sep='\n')
print('Выбор нескольких элементов: ', s[["a", "d"]], sep='\n')
print('Срез: ', s[1:], sep='\n')
print('Поэлементное сложение: ', s + s, sep='\n')
Объект класса DataFrame можно создать используя словарь Python:
table_dict = {"student": ["Студент_1", "Студент_2", "Студент_3"],
"math": [5, 5, 4],
"physics": [4, 5, 4]}
students = pd.DataFrame(table_dict)
print(students)
Если мы попытаемся создать датафрейм из списков различной длины, то получим ошибку, т. е. списки из которых формируются значения в столбцах должны быть одинаковой длины.
table_dict = {"student": ["Студент_1", "Студент_2", "Студент_3", "Студент_4"],
"math": [5, 5, 4],
"physics": [4, 5, 4]}
students = pd.DataFrame(table_dict)
print(students)
Значения индексов и столбцов датафрейма моно просмотреть с помощью атрибутов .index и .columns, соответственно.
print(students.index)
print(students.columns)
Выполнить переиндексацию (в качестве индексов будут номера строк начиная с 0) можно с помощью df.reset_index().
students.reset_index()
Если необходимо в качестве индекса установить какой-либо из столбцов, то это можно сделать с помощью:
students.set_index('student')
Значение индекса можно заменить следующим способом:
students.index = ["A", "B", "C"]
print(students)
Загрузка данных¶
Данные могут храниться в различных форматах. Наиболее популярными являются CSV, Excel, JSON, реьд и другие.
Для загрузки данных из .csv файла используется функция read_csv(). Аргумент file является строкой, в которой записан путь до файла с датасетом. Для записи данных из DataFrame в CSV-файл используется метод to_csv(file).
У этой функции много аргументов (см. документацию), первый из которых - название считываемого файла с данными, второй - sep - разделитель, по умолчанию это запятая.
Данные в csv файлах (обычно, но не всегда) записаны в виде "таблицы". Одна запись - одна строка, данные столбцов разделяются запятыми.
Чтобы загрузить Excel файл потребуется функция read_excel(). Для записи данных из DataFrame в Excel-файл используется метод to_excel().
Из формата JSON можно загрузить данные с помощью функции read_json(). Для записи данных из DataFrame в JSON используется метод to_json().
Для работы с файлом его, как и обычный текстовый файл (впрочем, это и есть обычный текстовый файл!), удобнее положить в ту же директорию, где лежит ноутбук. Затем вы можете прямо через jupyter открыть файл csv и увидеть, как он выглядит.
from io import StringIO
df = pd.DataFrame([['a', 'b'], ['c', 'd']],
index=['row 1', 'row 2'],
columns=['col 1', 'col 2'])
df.to_json(orient='index')
Скачайте файл telecom_churn.csv для выполнения последующего кода
f = pd.read_csv("telecom_churn.csv")
Обзор данных¶
Посмотреть начало и конец таблицы (по умолчанию 5 строк, но это настраиваемый параметр) - функции head и tail. Описание данных - describe, info. Размер таблицы - shape, названия колонок - columns. Обратите внимание, что columns возвращает итерируемый объект.
f.head()
f.head(7)
f.tail()
f.tail(7)
Вывести заданное число случайно выбранных строк можно через метод .sample().
f.sample(5)
Для того, чтобы при каждом запуске кода отбирались одни и теже строки нужно зафиксировать random_state.
f.sample(5, random_state=123)
f.shape
f.columns
f.describe()
f.info()
Небольшое, но важное отступление. Глубоко понимать это пока не обязательно. Обратите внимание, что функция print возвращает строковое представление объекта и она не необходима, чтобы вывести объект. 2 ячейки сработают одинаково:
a = 1
print(a)
a = 1
a
При этом для некоторых объектов (для которых стандартные функции __str__ и __repr__ возвращают разные значения) два способа вывода сработают несколько по-разному:
a = np.array([1.0, 2.0])
print(a)
a = np.array([1.0, 2.0])
a
Такая же ситуация с pandas dataframe'ами. Без print получится красивее, чем с ним, но фактически это одно и то же:
print(f)
f
display(f)
2 варианта синтаксиса для просмотра содержимого столбца:
f.State
f['State']
Обратите внимание, что таблица и столбцы имеют разный тип:
type(f), type(f.State), type(f['State'])
Тем не менее, мы можем сделать именно датафрейм из одного столбца следующим образом:
f[['State']]
type(f[['State']]), type(f['State'])
Это способ обращения к "срезам" таблицы по названиям столбцов. Еще пример:
f[['State', 'Churn']]
Просмотр уникальных значений в столбце и их числа:
f.State.unique()
Подсчитать число уникальных значений можно через метод .value_counts().
f.State.value_counts()
f.State.nunique()
Обращение к элементу объекта pd.Series:
f.State[0]
Полный набор функций, применимых к столбцу можно посмотреть следующим образом. Здесь используется стандартная функция __dir__(), которая выводит список атрибутов и методов объекта.
f.State.__dir__()
С pd.DataFrame привычный способ обращения по индексам не сработает:
f[0][0]
Основной способ - использование loc и iloc. loc дает индексацию по "настоящим" индексам - названиям столбцов и названиям строк. В нашем случае, названия строк - целые числа, как обычные индексы. iloc позволяет осуществлять стандартную численную индексацию, как для numpy массивов.
f.iloc[0] # первая строка
f.iloc[0][0]
f.iloc[0, 0]
f.iloc[[0, 0]]
f.iloc[[1, 2, 5, 1]]
f.loc[[1, 2, 5, 1]]
f.loc[[1, 2, 5, 1], ["State", "Account length"]]
f.iloc[[1, 2, 5, 1], [0, 1]]
Следующая ячейка вернет ошибку:
f.iloc[[1, 2, 5, 1], ["State", "Account length"]]
f.iloc[:5, :4]
f.loc[:, ["State", "Account length"]]
f.loc[4:9, ["State", "Account length", "Area code", "International plan"]]
f.loc[4:9, "State": "International plan"]
Обратите внимание, что loc включает и строки соответствующие и начальному, и конечному индексам.
Сохраним этот датафрейм в новую переменную и рассмотрим отличие loc и iloc в работе по целочисленным индексам строк
new_f = f.loc[4:9, ["State", "Account length", "Area code", "International plan"]]
new_f.iloc[:5]
new_f.loc[:5]
Упражнение 1. Объясните различие поведения loc и iloc для new_f.
Создание датафрейма и присваивание по индексам¶
df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]}, index = ["first", "second", "third"])
df
df.iloc[0, 0] = 100
df
df.loc["second", "B"] = 31
df
df.iloc[2] = 5
df
df.loc["B"] = 0
df
df.loc[:, "B"] = 0
df
df = pd.DataFrame({"A": [1, 2, 3, 4], "B": [5, 6, 7, 8]})
df
df.iloc[1:2, :] = -1
df
df.loc[1:2, :] = -2
df
Еще раз убеждаемся, что loc включает оба конца "среза", а iloc - полуинтервал, как например обычные срезы.
Создание нового столбца¶
df["C"] = 1
df
df["D"] = df["A"] + df["B"]
df
df["E"] = df["D"].apply(lambda x: x ** 2)
df
df["F"] = df[["A", "C", "E"]].apply(lambda x: x.sum(), axis=1)
df
Упражнение 2. Объясните последний результат.
Срезы с условиями и аггрегация¶
f[f.Churn == True]
f[f["Area code"] == 408]
f[f["Area code"] == 408].loc[3]
f[f["Area code"] == 408].loc[3] == f[f["Area code"] == 408].iloc[0]
f[f.Churn == True]
f[f.Churn == True].mean(numeric_only=True)
f[f.Churn == True].sum()
Выборка по нескольким условиям требует скобок, т.к. операции &, |, ~ (и, или, не) имеют более высокий приоритет, чем ==, >=, <= и арифметические:
f[(f.Churn == True) & (f["Area code"] == 408)]
Можем посмотреть средние показатели лояльных и нелояльных клиентов (столбец "Churn" показывает, ушел клиент от нас как от оператора связи или нет).
loyal = f[f.Churn == False].mean(numeric_only=True)
unloyal = f[f.Churn == True].mean(numeric_only=True)
pd.DataFrame({"loyal": loyal, "unloyal": unloyal})
groupby позволяет делать группировку по уникальным значениям столбца, которые будут играть роль индекса в полученном датафрейме. При этом также нужна функция агрегации (mean, sum, max и прочие), чтобы отобразить полученный объект.
f.groupby("Churn")
f.groupby("Churn").mean(numeric_only=True)
Здесь мы сделали агрегацию всех столбцов функцией mean. Часто нам нужна агрегация по конкретному или конкретным столбцам. Посмотрим, например, в каком штате больше всего разговаривают по телефону:
f.groupby(["State"]).agg({"Total day minutes": "mean"})
Максимальное значение времени:
f.groupby(["State"]).agg({"Total day minutes": "mean"}).max()
Чтобы найти сам штат, можем отсортировать данные:
f.groupby(["State"]).agg({"Total day minutes": "mean"}).sort_values(by="Total day minutes", ascending = False)
Пример. Как распределен отток клиентов по штатам? В каких штатах он больше среднего?
f.groupby(["State"]).agg({"Churn": "mean"})
f.groupby(["State"]).agg({"Churn": "mean"}).loc["LA"]
f.groupby(["State"]).agg({"Churn": "mean"}).mean()
f.groupby(["State"]).agg({"Churn": "mean"}).mean()[0]
m = f.groupby(["State"]).agg({"Churn": "mean"}).mean()[0]
new_dataframe = f.groupby(["State"]).agg({"Churn": "mean"})
new_dataframe[new_dataframe.Churn > m]
Упражнения.¶
Упражнение 3. Найдите среднее количество звонков Total day calls для всего датафрейма.
Упражнение 4. Найдите среднее количество звонков Total day calls для любого выбранного вами штата.
Упражнение 5. Создайте датафрейм, в котором будет среднее количество звонков Total day calls для каждого штата.
Упражнение 6. Оставьте в созданном датафрейме строки только с теми штатами, где количество звонков Total day calls больше среднего по исходному датафрейму.
Упражнение 7. Создайте датафрейм, в котором будует средние количества звонков Total day calls и Total eve calls для каждого штата.
Упражнение 8. Создайте датафрейм, в котором будует средние количества звонков Total day calls и Total eve calls для каждого штата, а также столбец со значениями True и False - ответом на вопрос, больше ли дневных звонков, чем вечерних.
Упражнение 9. Найти долю клиентов (отношение их числа к общему количеству клиентов) с international plan и voice mail plan.
Упражнение 10. Найти число уникальных значений Area code.
Упражнение 11. Вывести DataFrame из 2 столбцов: число звонков в поддержку; число клиентов, звонивших столько раз. Подсказка: используйте функцию агрегации count.
Упражнение 12. Вывести DataFrame из 2 столбцов: число звонков в поддержку; доля оттока (Churn). Построить график.
Упражнение 13. Найти среднюю длительность международного (intl) звонка.
Упражнение 14*. Какие звонки дольше - дневные, вечерние или ночные? Ответ привести в формате DataFrame $3*3$: строки - число минут, число звонков, среднее время звонка. Столбцы - день, вечер, ночь
Упражнение 15. Сравнить Total day charge для оставшихся и ушедших клиентов.
Упражнение 16. Отсортриуйте штаты по Total day charge (по возрастанию).
Упражнение 17. Сделайте агрегацию по средним показателям для каждой Area code.
Упражнение 18. Выведите датафрейм размера $3*2$: столбцы State, Churn для 100, 102 и 104 строк исходного датафрейма.
Упражнение 19. Создайте датафрейм из 2 столбцов, заполненных произвольными числами. Добавьте третий столбец, равный их сумме квадратов.
Упражнение 20. Добавьте к созданному датафрейму четвертый столбец, равный среднему значению первых трех, используя функцию mean.