Дорогой друг!

Специально для тебя, есть готовое переработанное решение в формате ."IPYNB" (.ipynb), но оно платное.

При получении вами критического (красного) замечания по проекту, проведу правки.

Решение сдесь: Добавь в корзину - Перейди в корзину - Укажи Email - Оплатить по СБП - СБП (приложение вашего банка) - Получи решение на Email

Не можешь оплатить, тогда через "Помощь" запроси другую систему оплаты (Webmoney, Alipay, Telegram (USDT, TON)).

Внимательно: Ниже представлено правильное решение, но его посмотрело уже больше 5000 человек. Думаю, что им можно пользоваться только для руководства при написании самостоятельного проекта, но сдавать без правок уже нельзя, есть вероятность получить замечание от опытных ревьюеров: "Это плагиат! Переделайте решение" и потом думать как всё исправить. 

Поэтому принято решение переделать проект заново и ограничить доступ к нему введя символическую оплату. Также поступлю и с последующими проектами. За готовым решением переходи по ссылке выше.

Вернуться в раздел: Сборный Проект — 1

Вернуться в оглавление: Я.Практикум

import pandas  as pd
import matplotlib.pyplot as plt
import numpy as np
import seaborn as sns
from scipy import stats as st

1.2 Чтение данных

df = pd.read_csv('/datasets/games.csv')
df.head()

1.3 Получение общей информации о данных

df.info()

Вывод по ознакомлению с данными:

  • Всего в таблице 16715 строк и 11 колонок в шести колонках имеются пропуски в данных
  • Названия колонок имеют нарушение стилей
  • Ряд колонок имеет неверный тип
  • Данных достаточно для проведения исследования
2. Предобработка данных
 
2.1 Переименование столбцов
df.columns
df.columns = df.columns.str.lower()
df.columns

2.2 Удаление полных дубликатов

df = df.drop_duplicates()

print(df[df.duplicated(['name', 'platform', 'year_of_release'])].count())
print(df[df.duplicated(['name', 'platform', 'year_of_release'])])
df = df.drop_duplicates(['name', 'platform', 'year_of_release'])
print(df[df.duplicated(['name', 'platform', 'year_of_release'])].count())

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

2.3 Обработка пропусков
 
Обработка name
print('В столбце `name` нет значений в ', (df['name'].isnull().sum() / len(df['name'])*100).round(3), '% данных. \nПри значении меньше 1 % примем решение удалить строки с пропусками в столбце `name` \nТогда всего пропусков останется \n')
df = df.dropna(subset=['name'])
df.isna().sum()

Обработка year_of_release

print('В столбце `year_of_release` нет значений в ', (df['year_of_release'].isnull().sum() / len(df['name'])*100).round(3), '% данных')
df = df.dropna(subset=['year_of_release'])
df.isna().sum()

Обработка critic_score

df['critic_score'].isna().sum() / len(df['name'])*100

Количество пропусков больше 51 %. Заменить на медиану и среднее нецелесообразно. Пометим отрицательным значением "-1"

df['critic_score'] = df['critic_score'].fillna(-1)

Обработка user_score

df['user_score'].isna().sum() / len(df['name'])*100

Количество пропусков больше 40 %. Заменить на медиану и среднее нецелесообразно. Пометим отрицательным значением "-1"

df['user_score'] = df['user_score'].fillna(-1)
df['user_score'].value_counts() 
df[df['user_score']=='tbd']['user_score'].count() / len(df['name'])*100

В столбце имеется оценка tbd не описанная в полученных данных, при этом ее значение больше 14%. Игнорировать это нельзя обозначим как "-1"

df.loc[df['user_score'] == 'tbd', 'user_score'] = -1
df['user_score'].value_counts() 

Обработка rating

df['rating'].isna().sum() / len(df['name'])*100

Количество пропусков больше 40 %. Заменить на медиану и среднее нецелесообразно. Пометим что возрастная категория неизвестна "unknown"

df['rating'] = df['rating'].fillna('unknown')
df.isna().sum()

2.4 Преобразование типов столбцов

df.info()
df['year_of_release'] = (df['year_of_release']).astype('int')
df['critic_score'] = (df['critic_score']).astype('int')
df['user_score'] = (df['user_score']).astype('float')

df.info()

2.5 Посчитаем суммарные продажи во всех регионах и запишем их в отдельный столбец.

df['total_sales'] = data.loc[:,['na_sales','eu_sales', 'jp_sales', 'other_sales']].sum(axis=1)
df.head()

Вывод по предобработке данных:

  • Явные дубликаты не обнаружены
  • Удалены строки с неявными дубликатами поиск одновременно по 'name', 'platform', 'year_of_release'
  • По столбцу name удалены 2 строки с пропусками
  • По столбцу year_of_release принято решение удалить строки с пропусками
  • В столбце critic_score количество пропусков больше 51% они обозначены как -1
  • В столбце user_score количество пропусков больше 40% они обозначены как -1, неописанное значение tbd обозначено как -2
  • В столбце rating пропуски обозначены как unknown
  • Столбцы year_of_releasecritic_score приведены к int типу, user_score к float в соответствии с данными содержащимися в них.
  • Пропуски могли появиться из за сбора данных из различных источников, которые не согласованы между собой.
  • Посчитали суммарные продажи во всех регионах записали в total_sales

3. Проведем исследовательский анализ данных

3.1 Исследование выпуска игр по годам
(
    df
    #.query('year_of_release > -1') #Строки с пропусками удалены
    .pivot_table(index='year_of_release', values='name', aggfunc='count')
    .plot(kind='bar' , grid=True, figsize=(10, 5))
)
plt.title('Количество продаж по годам')
plt.xlabel('Год')
plt.ylabel('Количество продаж')
plt.show()

df[df['year_of_release'] > -1]['year_of_release'].describe()

Резюме по выпуску игр по годам

  • В таблице представлены игры с выпуском с 1980 по 2016 год.
  • Присутствуют игры у которых не указан год выпуска в количестве 269
  • Рост числа выпуска игр приходится на 1995 год, с максимумом примерно в 2007 году после чего прослеживается спад.
  • С 2012 года объем выпуска игр стабилизировался и год от года отличается несильно.
  • В целях исследования логично выбрать период в 5 - 10 лет (старые игры забываются, новые сильнее влияют на рынок).
3.2 Исследование продаж по платформам.
(
    df.pivot_table(index='platform', values='total_sales', aggfunc='sum')
    .sort_values(by='total_sales', ascending=False)
    .plot(kind='bar' , grid=True, figsize=(10, 5))
)
plt.title('Количество суммарных продаж в зависимости от платформы')
plt.xlabel('Платформа')
plt.ylabel('Суммарные продажи')
plt.show()

Наибоше суммарных продаж зафиксировано у 6 платформ

top_sales = df.pivot_table(index='platform', values='total_sales', aggfunc='sum').sort_values(by='total_sales', ascending=False).reset_index().head(6)['platform'].tolist()
top_sales

 

for name in top_sales:
    (
    df.query('platform == @name and year_of_release > -1')
    .pivot_table(index = 'year_of_release', values = 'total_sales', aggfunc = 'sum')
    .sort_values('year_of_release', ascending = False)['total_sales']
    .plot(figsize = (10, 5), label=name)
    )
    plt.title('Жизненный цикл шести платформ с максимальными продажами')
    plt.xlabel('Год выпуска')
    plt.ylabel('Суммарные продажи')
    plt.legend() 

Проанализируем артефакт на графике по консоли DS

print(df.query('platform == "DS" and year_of_release < 1990'))

В данных только одна строка и это явная ошибка возникшая при заполнении данных в столбце nameю Принимаем решение удалить эту строку по её индексу и перестроить график жизненного цикла консолей.

df = df.drop(index = 15957)
for name in top_sales:
    (
    df.query('platform == @name')
    .pivot_table(index = 'year_of_release', values = 'total_sales', aggfunc = 'sum')
    .sort_values('year_of_release', ascending = False)['total_sales']
    .plot(figsize = (10, 5), label=name)
    )
    plt.title('Жизненный цикл шести платформ с максимальными продажами')
    plt.xlabel('Год выпуска')
    plt.ylabel('Суммарные продажи')
    plt.legend() 

Расчитаем время жизненного цикла по всем данным

time_life = df.pivot_table(index='platform', values='year_of_release', aggfunc=['min', 'max']).reset_index()
# в срок жизни платформы, должны входить границы диапазона, поэтому +1
time_life['life'] = time_life['max'] - time_life['min'] + 1 
print('Медианное значение жизненного цикла по всей выборке', time_life['life'].median(), 'лет')

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

t = np.ceil(time_life['life'].median()/2)
print('Определим актуальный период за последние', t, 'года')

Вывод по исследованию платформ

Из графиков видно, что от срока появления платформы до их исчезновения как правило проходит 10 лет, следовательно для нас интересны платформы релдиз которых состоялся 3-5 годами ранее и которые активно растут в продажах. Определим, что актуальный период составляет 4 последних года.

3.3 Исследование перспективных платформ
actual_platforms = (df
    .query('year_of_release >= 2016-@t')
    .pivot_table(index='platform', values='total_sales', aggfunc='sum')
    .sort_values(by='total_sales', ascending=False)
    .reset_index().head(10)['platform'].tolist()
    )                         
actual_platforms
actual_df = df.query('year_of_release >= 2016-@t').copy().reset_index(drop= True)
actual_df.head()
for name in actual_platforms:
    (
    actual_df.query('platform == @name')
    .pivot_table(index = 'year_of_release', values = 'total_sales', aggfunc = 'sum')
    .sort_values('year_of_release', ascending = False)['total_sales']
    .plot(kind='line', figsize = (10, 5), label=name)
    )
    plt.title('Жизненный цикл всех платформ за актуальный период')
    plt.xticks(np.arange(2012, 2018, step=1))
    plt.xlabel('Год')
    plt.ylabel('Суммарные продажи')
    plt.legend() 

Из графика видно, что на 2016 год нет явно возрастающих продаж по консолям, продажи падают на всех консолях. Более или менее перспективными являются консоли PS4 и XOne но у них также прослеживаются уменьшения продаж.

3.4 Исследуем "Boxplot" - «ящик с усами» по глобальным продажам игр в разбивке по платформам актуального периода.
plt.figure(figsize=(12,6))
sns.boxplot(data=actual_df, x='platform', y='total_sales')
plt.title('Продажи по перспективным платформам с выбросами', fontsize=15)
plt.xlabel('Платформа', fontsize=12)
plt.ylabel('Продажи по платформам',fontsize=12)
actual_df['total_sales'].describe()
plt.figure(figsize=(12,6))
ax = sns.boxplot(data=actual_df, x='platform', y='total_sales')
ax.set(ylim=(0,2))
plt.title('Продажи по перспективным платформам с крупными `Boxplot`', fontsize=15)
plt.xlabel('Платформа', fontsize=12)
plt.ylabel('Продажи по платформам',fontsize=12)

Вывод по построению диаграммы Boxplot

Описательная статистика данных за актуальный период указывает, что:

  • Медианное значение по всей выборке составляет 0,12 но у большинства консолей данный показатель не совпадает с основным.
  • По первой и третьей квартили также наблюдается большой разброс.
  • По всем консолям присутствуют выбросы по продажам.
3.5 Влияние отзывов на продажи

Отберем 5 платформ с максимальными продажами

actual_platforms_5 = (actual_df
    
    .pivot_table(index='platform', values='total_sales', aggfunc='sum')
    .sort_values(by='total_sales', ascending=False)
    .reset_index().head(5)['platform'].tolist()
    )                         
actual_platforms_5

Построим диаграмму распределения и вычислим корреляцию между оценкой пользователей и продажами в разрезе отдельных платформ

for name in actual_platforms_5:
    (
    actual_df
        .query('platform == @name and user_score > -1')
        .plot(x='user_score', 
          y='total_sales', 
          kind='scatter', 
          figsize=(10, 5), 
          color='blue')
    )
    plt.title(f'Диаграмма распределения между оценками пользователей и продажами по платформе {name}')
    plt.xlabel('Оценка пользователей')
    plt.ylabel('Продажи')
    plt.legend([name])
    print(f'Корреляция между оценками пользователей и продажами по платформе {name}  =', actual_df.query('platform == @name and user_score > -1')['user_score'].corr(actual_df.query('platform == @name and user_score > -1')['total_sales']))
    plt.show()
Корреляция между оценками пользователей и продажами по платформе PS4  = -0.031957110204556376
Корреляция между оценками пользователей и продажами по платформе PS3 = -0.006205767686051523
Корреляция между оценками пользователей и продажами по платформе X360 = 0.006163978646213326
 
Корреляция между оценками пользователей и продажами по платформе 3DS  = 0.19758263411038599
Корреляция между оценками пользователей и продажами по платформе XOne = -0.06892505328279414
Диаграммы распределения явно показывают на слабую связь между оценкой пользователей и продажами в разрезе отдельных платформ, соответствующий коэффициент корреляции подтверждает это (лишь в некоторых случаях он приближается к 0,2).
 

Построим диаграмму распределения и вычислим корреляцию между оценкой критиков и продажами в разрезе отдельных платформ

for name in actual_platforms_5:
    (
    actual_df
        .query('platform == @name and critic_score > -1')
        .plot(x='critic_score', 
          y='total_sales', 
          kind='scatter', 
          figsize=(10, 5), 
          color='green')
    )
    plt.title(f'Диаграмма распределения между оценками критиков и продажами по платформе {name}')
    plt.xlabel('Оценка критиков')
    plt.ylabel('Продажи')
    plt.legend([name])
    print(f'Корреляция между оценками критиков и продажами по платформе {name}  =', actual_df.query('platform == @name and critic_score > -1')['critic_score'].corr(actual_df.query('platform == @name and critic_score > -1')['total_sales']))
    plt.show()
Корреляция между оценками критиков и продажами по платформе PS4  = 0.40656790206178095
 
 
Корреляция между оценками критиков и продажами по платформе PS3  = 0.3341517575509856
 
 
Корреляция между оценками критиков и продажами по платформе X360  = 0.3605734888507336
 
 
Корреляция между оценками критиков и продажами по платформе 3DS  = 0.3208028695360785
 
 
Корреляция между оценками критиков и продажами по платформе XOne  = 0.4169983280084017
 
Диаграммы распределения явно показывают на умеренную связь между оценкой критиков и продажами в разрезе отдельных платформ, соответствующий коэффициент корреляции подтверждает это (колеблется от 0,32 до 0,42).
 

Сделаем аналогичные расчеты для всего набора данных.

print('Общая корреляция между оценками пользователей и продажами = ', actual_df.query('user_score > -1')['user_score'].corr(actual_df['total_sales']))
print('Общая корреляция между оценками критиков и продажами = ', actual_df.query('critic_score > -1')['critic_score'].corr(actual_df['total_sales']))
(
 actual_df.query('user_score > -1')
    .plot(x='user_score', 
          y='total_sales', 
          kind='scatter', 
          figsize=(10, 5), 
          color='blue')
)
plt.title('Диаграмма распределения между оценками пользователей и продажами', fontsize=15)
plt.xlabel('Оценка пользователей', fontsize=12)
plt.ylabel('Продажи',fontsize=12)
plt.show()
(
 actual_df.query('critic_score > -1')
    .plot(x='critic_score', 
          y='total_sales', 
          kind='scatter', 
          figsize=(10, 5), 
          color='green')
)
plt.title('Диаграмма распределения между оценками критиков и продажами', fontsize=15)
plt.xlabel('Оценка критиков', fontsize=12)
plt.ylabel('Продажи',fontsize=12)
plt.show()
(actual_df
 .pivot_table(index='genre', values='total_sales', aggfunc='sum')
 .sort_values('total_sales', ascending=False)
 .plot(kind='bar', y='total_sales', figsize=(10, 5), legend=False,title = 'Прибыльность жанров в актуальном периоде')
 .set(xlabel='Жанр игры', ylabel='Всего продаж по жанру'))

plt.show()
Построим диаграмму размаха
plt.figure(figsize=(12,6))
ax = sns.boxplot(data=actual_df, x='genre', y='total_sales')
plt.title('Продажи по перспективным жанрам', fontsize=15)
plt.xlabel('Жанр', fontsize=12)
plt.ylabel('Продажи по жанрам',fontsize=12)
Text(0, 0.5, 'Продажи по жанрам')
 
plt.figure(figsize=(12,6))
ax = sns.boxplot(data=actual_df, x='genre', y='total_sales')
ax.set(ylim=(0,1.5))
plt.title('Продажи по перспективным жанрам', fontsize=15)
plt.xlabel('Жанр', fontsize=12)
plt.ylabel('Продажи по жанрам',fontsize=12)
Text(0, 0.5, 'Продажи по жанрам')