Мы собрали данные, которые будем очищать, с помощью веб-скрапинга в Часть 1 и Часть 2 этой серии. Всего у нас есть 3 набора данных, которые мы, наконец, объединим в один к концу этого раздела.
Очистка данных mvps
Начнем с данных mvps. Импортируйте пакет pandas и прочитайте данные. Я сохранил свой файл как mvps.csv. Ваш файл можно сохранить по-другому. Убедитесь, что он находится в том же каталоге, что и ваш файл блокнота Jupyter, иначе вам придется указывать полный путь, что меня очень раздражает.
import pandas as pd # read in the data mvps = pd.read_csv("mvps.csv") players = pd.read_csv("player_stats.csv") teams = pd.read_csv("teams.csv")
Далее мы просмотрим выборку данных из всех трех наборов данных. Мы включим строку кода, которая показывает все столбцы вместо многоточия (…) Это потому, что я хотел бы сравнить столбцы из всех трех наборов данных.
# view a sample of the data pd.pandas.set_option('display.max_columns', None) #display all column names mvps.sample()
Вы можете сделать то же самое для остальных наборов данных.
Поскольку данные о mvps и игроках в основном относятся к игрокам, мы удалим из набора данных mvps некоторые столбцы, которые уже есть в наборе данных об игроках. Во-первых, мы найдем общие столбцы в обоих, используя метод пересечения ().
common_columns = mvps.columns.intersection(players.columns) print(common_columns)
Далее мы выберем подмножество столбцов, которые хотим сохранить.
- Pts Won — очки, набранные в голосовании MVP.
- Pts Max — максимальное количество набранных баллов
- Доля — выигранные очки/макс. очки
mvps = mvps[["Player", "Pts Won", "Pts Max", "Share", "Year"]] mvps.head()
Очистка данных игроков
Далее мы очистим данные игрока, а затем объединим их с данными mvps, чтобы мы могли получить голоса для каждого игрока. Мы удалим некоторые ненужные столбцы.
del players["Unnamed: 0"] del players["Rk"] # confirm players.head()
Из общих столбцов мы увидели столбцы игрока и года. Мы проверим их, чтобы убедиться, что они в правильном формате. Мы объединим игроков и mvps на основе этих двух столбцов.
players["Player"].head(50)
Мы заметили, что у некоторых имен есть звездочки * в конце. Мы должны удалить звездочки *, так как они вызовут проблемы во время слияния. Это потому, что панды будут интерпретировать их как разные имена. Мы используем метод python str.replace().
По умолчанию панды используют * в регулярных выражениях для метода замены, а * похож на ключевое слово (из-за отсутствия лучшего слова) в регулярных выражениях. Поэтому мы устанавливаем regex=False, чтобы панды рассматривали * как обычный *
players["Player"] = players["Player"].str.replace("*","", regex=False) #confirm the change players.head()
Теперь проверяем дубликаты.
players.duplicated().sum()
Есть более 466 повторяющихся строк. Поскольку их слишком много для отображения, я провел визуальную проверку данных, отобразив первые 20 записей.
Обратите внимание, что у игрока 8. Грега Андерсона есть несколько рекордов за 1991 год. Это потому, что в том году он играл за разные команды. Чтобы подтвердить это, мы создадим группу для каждого игрока и года. Это возвращает все 4 записи Грега Андерсона.
players.groupby(["Player", "Year"]).get_group(("Greg Anderson", 1991))
Мы напишем функцию, которая перебирает каждую группу и следит за тем, чтобы в каждой группе была только одна строка. Затем мы используем метод apply(), чтобы применить эту функцию к каждой группе.
def single_row(players): #if there's only one row for the player, return it if players.shape[0] == 1: return players else: row = players[players["Tm"] == ["TOT"] row["Tm"] = players.iloc[-1,:]["Tm"] # replace with last team played in return row # apply the function to each group players = players.groupby(["Player", "Year"]).apply(single_row)
Затем мы смотрим на первые 20 строк, чтобы убедиться, что изменения были применены. Используйте метод head(). Вывод показывает каждого игрока со всеми годами, в которые он играл.
У нас есть проблема. Функция groupby добавила 2 дополнительных индексных столбца. Теперь у нас есть многоуровневый индекс, потому что мы разделили его на группы и снова объединили. Но они нам не нужны, поэтому мы их опустим. Запустите приведенный ниже код дважды (в отдельных ячейках). Затем снова отобразите кадр данных, чтобы убедиться, что они исчезли.
players.index = players.index.droplevel()
Для дальнейшего подтверждения вы можете проверить записи Грега Андерсона, чтобы убедиться, что есть только одна запись за 1991 год вместо 4.
players[players["Player"] == "Greg Anderson"]
Объединение данных об игроках и mvps
Мы объединим два набора данных в столбцах Player и Year. Мы сделаем это с помощью внешнего слияния. Причина в том, что не все строки в столбце Player находятся в столбце Player mvps.
Данные mvps содержат только игроков, которые выиграли MVP, в то время как данные игроков содержат всех игроков, независимо от того, выиграли ли они MVP или нет. Внешнее слияние все равно сохранит строки, даже если они не будут найдены в другом наборе данных.
combined = players.merge(mvps, how="outer", on=["Player", "Year"]) combined.head()
Игроки с NaN в последних 3 столбцах никогда не становились MVP. Но мы можем проверить игроков, у которых есть, просто чтобы убедиться, что слияние работает правильно.
combined[combined["Pts Won"] > 0]
Вы можете заполнить последние 3 столбца нулями для игроков, которые никогда не выигрывали MVP, если хотите. Технически значения не отсутствуют.
Очистка данных команды
Мы уже прочитали в данных команд, но вы можете, если вы еще этого не сделали. Мы рассмотрим первые 20 строк фрейма данных. Вы заметите, что строки заголовков появляются после каждых 6 или 7 записей. Нам нужно удалить их.
# ~ means not teams = teams[~teams["W"].str.contains("Division")]
Названия некоторых команд имеют в конце звездочку *. Мы будем использовать метод замены, как и раньше, и удалим расширение *.
# remove * from team names teams["Team"] = teams["Team"].str.replace("*","", regex=False) # confirm teams.head()
Еще одна проблема, с которой мы столкнулись, связана с названиями команд. Когда мы получаем уникальные названия команд в наборе данных команд и имена в комбинированном наборе данных, мы понимаем, что имена из комбинированного набора данных сокращены, а имена в наборе данных команд — нет. Это вызовет проблему при слиянии, поэтому нам нужно это исправить.
Один из способов сделать это — сопоставить аббревиатуры с их полными именами, сохранить их в виде файла и затем использовать. Это уже было сделано за вас(вы можете сказать спасибо подсказкой ниже ;) )Файл называется nicknames.txt и вы можете скачать это из моей Telegram группы.
nicknames = {} with open("nicknames.txt")as f: lines = f.readlines() for line in lines[1:]: # skip header row abbrev, name = line.replace("\n","").split(",") nicknames[abbrev] = name # view the dictionary nicknames
Мы создадим новый столбец с именем Team в combined, а затем воспользуемся функцией map(), которая применит словарь nicknames
к каждому элементу в столбце 'Tm' из объединенного набора данных.
По умолчанию новый столбец будет в конце фрейма данных. Но я бы хотел, чтобы он был рядом со столбцом Tm
, чтобы было легко сопоставить имена и убедиться, что они верны. Я буду использовать метод insert() для создания нового столбца.
combined.insert(4, "Team", combined["Tm"].map(nicknames)) combined.head()
Данные наших команд чисты и готовы. Мы объединим его с объединенными данными, используя столбцы Team
и Year
. Таким образом, для каждой объединенной строки мы добавим строку из команд, соответствующих записи W/L.
stats = combined.merge(teams, how="outer", on=["Team","Year"]) stats del stats["Unnamed: 0"]
Затем вы можете подтвердить правильность типов данных и при необходимости изменить их. поскольку у нас так много столбцов, самый простой способ сделать это, как показано ниже. Столбцы, которые являются фактическими строками, будут вызывать ошибки, которые будут проигнорированы, поэтому тип данных не изменится.
stats = stats.apply(pd.to_numeric, errors="ignore") # confirm change stats.dtypes
Просмотрите столбцы, которые не изменились, и подтвердите правильный тип данных. Например, столбец ГБ должен быть числовым, но оставаться в виде строки. Мы подтвердим, а затем изменим, если это необходимо.
stats["GB"].unique()
В одном из значений есть прочерк, который означает, что у команды нет игр назад (GB), поэтому мы заменим это. Скопируйте и вставьте его в свой код. Это связано с тем, что некоторые персонажи не совсем такие, какими кажутся на экране. Если вы присмотритесь, то увидите, что оно длиннее тире, которое исходит от вашей клавиатуры.
stats["GB"] = stats["GB"].str.replace("—","0") stats["GB"].unique()
Подтвердите изменение, используя метод unique() еще раз. Преобразуйте столбец в числовой и подтвердите изменения
stats["GB"] = pd.to_numeric(stats["GB"]) stats.dtypes
Сохраните объединенные наборы данных в csv.
stats.to_csv("player_mvp_stats.csv")
Это конец нашей очистки данных. Вы можете приступить к изучению данных. Вот фора. Мы найдем игроков, набравших наибольшее количество очков, и нанесем их на гистограмму.
highest_scoring = stats[stats["G"] > 70].sort_values("PTS", ascending=False).head(10) highest_scoring
Постройте гистограмму того же
highest_scoring.plot.bar("Player", "PTS")
Что ж, это было весело и утомительно. Похлопайте мне и угостите меня кофе, если сможете.
Вы можете продолжить отсюда, если хотите. Но если вы хотите, чтобы мы сделали это вместе, прокомментируйте ниже; ). Мы можем провести исследование и анализ или построить модель машинного обучения для прогнозирования MVP на каждый год.
Ваше здоровье!