Агрегация на фреймах данных с горячим кодированием - PullRequest
0 голосов
/ 11 октября 2018

Скажем, у меня есть набор данных с 10000 фильмами в https://bpaste.net/show/05fa224794e4, с ошибкой набора данных, равной

tt0111161   The Shawshank Redemption (1994) 1994     9.2    619479  142 mins.   Crime|Drama
tt0110912   Pulp Fiction (1994) 1994     9.0    490065  154 mins.   Crime|Thriller
tt0137523   Fight Club (1999)   1999     8.8    458173  139 mins.   Drama|Mystery|Thriller
tt0133093   The Matrix (1999)   1999     8.7    448114  136 mins.   Action|Adventure|Sci-Fi
tt1375666   Inception (2010)    2010     8.9    385149  148 mins.   Action|Adventure|Sci-Fi|Thriller
tt0109830   Forrest Gump (1994) 1994     8.7    368994  142 mins.   Comedy|Drama|Romance
tt0169547   American Beauty (1999)  1999     8.6    338332  122 mins.   Drama
tt0499549   Avatar (2009)   2009     8.1    336855  162 mins.   Action|Adventure|Fantasy|Sci-Fi
tt0108052   Schindler's List (1993) 1993     8.9    325888  195 mins.   Biography|Drama|History|War
tt0080684   Star Wars: Episode V - The Empire Strikes Back (1980)   1980     8.8    320105  124 mins.   Action|Adventure|Family|Sci-Fi
tt0372784   Batman Begins (2005)    2005     8.3    316613  140 mins.   Action|Crime|Drama|Thriller
tt0114814   The Usual Suspects (1995)   1995     8.7    306624  106 mins.   Crime|Mystery|Thriller
tt0102926   The Silence of the Lambs (1991) 1991     8.7    293081  118 mins.   Crime|Thriller
tt0120338   Titanic (1997)  1997     7.4    284245  194 mins.   Adventure|Drama|History|Romance

У меня есть этот код, который загружает мой набор данных и вносит в него некоторые изменения

import pandas as pd
import numpy as np

headers = ['imdbID', 'title', 'year', 'score', 'votes', 'runtime', 'genres'] 
movies = pd.read_csv("imdb_top_10000.txt", sep="\t", header=None, names=headers, encoding='UTF-8')
movies.head()

one_hot_encoding = movies["genres"].str.get_dummies(sep='|')
movies = pd.concat([movies, one_hot_encoding], axis=1)

movies_top_250 = movies.sort_values('score', ascending=False).head(250)

Учитывая это

  • Мы хотим найти три лучших жанра из пула первых 250 лучших по рейтингу
  • Также среднее число фильмовв этих жанрах
  • Также мы хотим узнать среднюю оценку трех жанров с наихудшей оценкой в ​​этом пуле фильмов

Возможно, я думал о сводной таблице?Здесь используется только подмножество столбцов жанра.

pd.pivot_table(movies_top_250, values=['votes', 'Action', 'Adult'], index='title', aggfunc=np.sum).sort_values('votes', ascending=False)

                                                    Action  Adult   votes
title                                                                    
The Shawshank Redemption (1994)                          0      0  619479
The Dark Knight (2008)                                   1      0  555122
Pulp Fiction (1994)                                      0      0  490065
The Godfather (1972)                                     0      0  474189
Fight Club (1999)                                        0      0  458173
The Lord of the Rings: The Fellowship of the Ri...       1      0  451263
The Matrix (1999)                                        1      0  448114
The Lord of the Rings: The Return of the King (...       1      0  428791
Inception (2010)                                         1      0  385149
The Lord of the Rings: The Two Towers (2002)             1      0  383113
Forrest Gump (1994)                                      0      0  368994

Но это не говорит о том, какой жанр имеет большинство голосов.Кроме того,

movies.groupby('genres').score.mean()

Возвращает что-то вроде

genres
Action                                                   5.837500
Action|Adventure                                         6.152381
Action|Adventure|Animation|Comedy|Family|Fantasy         7.500000
Action|Adventure|Animation|Family|Fantasy|Sci-Fi         6.100000
Action|Adventure|Biography|Crime|History|Western         6.300000
Action|Adventure|Biography|Drama|History                 7.700000

Так что я не могу понять мою голову по этому поводу.По первому вопросу я подумываю получить что-то вроде

Genre          mean_score  votes_sum
Action           7.837500     103237
Adventure        6.152381     103226
Animation        5.500000     103275

Ответы [ 2 ]

0 голосов
/ 13 октября 2018

Вы можете использовать это решение oneline (экранирование строк только для красивого формата):

movies = \
    (movies.set_index(mv.columns.drop('genres',1).tolist())
    .genres.str.split('|',expand=True)
    .stack()
    .reset_index()
    .rename(columns={0:'genre'})
    .loc[:,['genre','score','votes']]
    .groupby('genre').agg({'score':['mean'], 'votes':['sum']})
    )

              score    votes
               mean      sum
genre                      
Action     8.425714  7912508
Adventure  8.430000  7460632
Animation  8.293333  1769806
Biography  8.393750  2112875
Comedy     8.341509  3166269
...

ОБЪЯСНЕНИЕ :

Основная проблемаэто кратные True значения, полученные в результате процесса one_hot_encoding по жанрам.Один фильм можно назначить одному или нескольким жанрам.Следовательно, вы не можете использовать методы агрегирования по жанрам.С другой стороны, использование поля genres в том виде, в каком оно есть, приведет к растворению результатов нескольких полов, как показано в вашем вопросе:

                                                           genres
Action                                                   5.837500
Action|Adventure                                         6.152381
Action|Adventure|Animation|Comedy|Family|Fantasy         7.500000
Action|Adventure|Animation|Family|Fantasy|Sci-Fi         6.100000
Action|Adventure|Biography|Crime|History|Western         6.300000
Action|Adventure|Biography|Drama|History                 7.700000

Обходной путь - дублирование строк, если найдено более одного пола.Используя комбинацию split с методом expand, установленным на True, вы можете создать несколько фреймов данных и затем разместить их в стеке.Например, фильм с двумя жанрами появится в двух результирующих кадрах данных, где каждый кадр данных представляет фильмы, назначенные каждому жанру.Наконец, после разбора вы можете агрегировать по полу с несколькими функциями.Я объясню шаг за шагом:

1.Получить 250 лучших фильмов (по баллу)

Загрузить данные:

import pandas as pd
import numpy as np

headers = ['imdbID', 'title', 'year', 'score', 'votes', 'runtime', 'genres'] 
movies = pd.read_csv("imdb_top_10000.txt", sep="\t", header=None, names=headers, encoding='UTF-8')

Обратите внимание, что в поле genres есть нулевые значения:

             imdbID             title  year  score  votes   runtime genres
7917  tt0990404  Chop Shop (2007)  2007    7.2   2104  84 mins.    NaN

Поскольку методы агрегированияс Pandas пропустит строки с любым нулевым значением, и у нас есть только 1 фильм с нулевым значением в этом поле, который можно установить вручную (проверено на Imdb):

movies.loc[movies.genres.isnull(),"genres"] = "Drama"

Теперь, как вы уже показали,нам нужны лучшие 250 фильмов по баллу:

movies = movies.sort_values('score', ascending=False).head(250)

2.Создать жанровое поле из жанров, используя split с расширением

2.1.Установить индекс

В качестве столбца оставить только поле жанров, остальные поля - индексом.Это для удобства работы над жанрами.

movies = movies.set_index(movies.columns.drop('genres',1).tolist())                                                                         
                                                                                     genres
imdbID    title                                 year score votes  runtime                  
tt0111161 The Shawshank Redemption (1994)       1994 9.2   619479 142 mins.     Crime|Drama
tt0068646 The Godfather (1972)                  1972 9.2   474189 175 mins.     Crime|Drama
tt0060196 The Good, the Bad and the Ugly (1966) 1966 9.0   195238 161 mins.         Western
tt0110912 Pulp Fiction (1994)                   1994 9.0   490065 154 mins.  Crime|Thriller
tt0252487 Outrageous Class (1975)               1975 9.0   9823   87 mins.     Comedy|Drama
(250, 1)

2.2.Разделить по жанрам

Это создаст N кадров данных из N итераций разделения.

movies = movies.genres.str.split('|',expand=True)

                                                                                       0  \
imdbID    title                                 year score votes  runtime              
tt0111161 The Shawshank Redemption (1994)       1994 9.2   619479 142 mins.    Crime   
tt0068646 The Godfather (1972)                  1972 9.2   474189 175 mins.    Crime   
tt0060196 The Good, the Bad and the Ugly (1966) 1966 9.0   195238 161 mins.  Western   
tt0110912 Pulp Fiction (1994)                   1994 9.0   490065 154 mins.    Crime   
tt0252487 Outrageous Class (1975)               1975 9.0   9823   87 mins.    Comedy   

                                                                                    1  \
imdbID    title                                 year score votes  runtime               
tt0111161 The Shawshank Redemption (1994)       1994 9.2   619479 142 mins.     Drama   
tt0068646 The Godfather (1972)                  1972 9.2   474189 175 mins.     Drama   
tt0060196 The Good, the Bad and the Ugly (1966) 1966 9.0   195238 161 mins.      None   
tt0110912 Pulp Fiction (1994)                   1994 9.0   490065 154 mins.  Thriller   
tt0252487 Outrageous Class (1975)               1975 9.0   9823   87 mins.      Drama   

...

2.3.Стек

Теперь у вас есть уникальное значение жанра для каждого фильма, где фильм может иметь более 1 строки, если было назначено более 1 жанра, вы можете сложить набор фреймов данных.Обратите внимание, что теперь у нас более 250 строк (662 строки), но мы имеем 250 отдельных фильмов.

movies = movies.stack()

imdbID     title                                  year  score  votes   runtime     
tt0111161  The Shawshank Redemption (1994)        1994  9.2    619479  142 mins.  0      Crime
                                                                                  1      Drama
tt0068646  The Godfather (1972)                   1972  9.2    474189  175 mins.  0      Crime
                                                                                  1      Drama
tt0060196  The Good, the Bad and the Ugly (1966)  1966  9.0    195238  161 mins.  0    Western
dtype: object
(662,)

3.Parse

Получите подходящую структуру данных перед агрегированием:

# Multiple index to columns
movies = movies.reset_index()

# Name the new column for genre
movies = movies.rename(columns={0:'genre'})

# Only wanted fields to be aggregated
movies = movies.loc[:,['genre','score','votes']]

     genre  score   votes
0    Crime    9.2  619479
1    Drama    9.2  619479
2    Crime    9.2  474189
3    Drama    9.2  474189
4  Western    9.0  195238
(662, 3)

4.Агрегат

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

movies = movies.groupby('genres').agg({'score':['mean'], 'votes':['sum']})

              score    votes
               mean      sum
genre                      
Action     8.425714  7912508
Adventure  8.430000  7460632
Animation  8.293333  1769806
Biography  8.393750  2112875
Comedy     8.341509  3166269
(21, 2)
0 голосов
/ 12 октября 2018

Настройка

import io

import numpy as np
import pandas as pd

colnames = ['imdbID', 'title', 'year', 'score', 'votes', 'runtime', 'genres']

data_url = 'https://bpaste.net/raw/05fa224794e4'

movies = pd.read_csv(data_url, sep="\t", header=None, names=colnames, encoding='UTF-8', index_col='imdbID')

И полезная функция

def arg_nlargest(x, n, use_index=True):
    if isinstance(x, pd.Series):
        x = x.values
    return np.argpartition(-x, n)[:n]

3 самых популярных жанра среди 250 лучших фильмов

Сначала получите 250 лучших фильмов:

top250_iloc = arg_nlargest(movies['score'], 250)
movies250 = movies.iloc[top250_iloc]

Далее мы расширяем жанры из каждого фильма в индикаторы, как вы это сделали

movies250_genre_inds = movies250["genres"].str.get_dummies(sep='|')

Наивный способ продолжить - это циклически проходить по столбцам индикаторов, собирая агрегаты для каждого жанра..

genre_agg = {}
for genre in movies250_genre_inds.columns:
    mask = movies250_genre_inds[genre].astype(bool)
    aggregates = movies250.loc[mask].agg({'score': 'mean', 'votes': 'sum'}) 
    genre_agg[genre] = aggregates.tolist()

genre_agg = pd.DataFrame.from_dict(genre_agg, orient='index', columns=['score_mean', 'votes_sum'])

genre3_iloc = arg_nlargest(genre_agg['score_mean'], 3)
genre3 = genre_agg.iloc[genre3_iloc].sort_values('score_mean', ascending=False)
...