Мы собрали данные, которые будем очищать, с помощью веб-скрапинга в Часть 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 на каждый год.

Ваше здоровье!