1 - Введение в Pandas

Pandas это очень мощная библиотека с множеством полезных функций, ею можно пользаться много лет так и не использовав весь ее потенциал. Цель воркшопа ознакомить вас основами, это:

  • Чтение и запись данных.

  • Пониманимание разных типов данных в Pandas.

  • Работа с текстовыми данными и timeseries.

  • Выбор данных.

  • Группировка.

Dataset

Мы будем использовать датасет Amazon Product с отзывами о продуктах на Амазоне, его собрал Julian McAuley.
Датасет выглядит таким образом:

reviewerID - ID of the reviewer, e.g. A2SUAM1J3GNN3B
asin - ID of the product, e.g. 0000013714
reviewerName - name of the reviewer
helpful - helpfulness rating of the review, e.g. 2/3
reviewText - text of the review
overall - rating of the product
summary - summary of the review
unixReviewTime - time of the review (unix time)
reviewTime - time of the review (raw)

Импорт pandas


In [57]:
import pandas as pd
print("Pandas version: {}".format(pd.__version__))

# опции отображения
pd.options.display.max_rows = 6
pd.options.display.max_columns = 6
pd.options.display.width = 100


Pandas version: 0.18.1

Чтение и запись данных


In [59]:
import gzip
# датасет на 47 мегабайт, мы возьмем только 10
review_lines = gzip.open('data/reviews/reviews_Clothing_Shoes_and_Jewelry_5.json.gz', 'rt').readlines(10*1024*1024)
len(review_lines)


Out[59]:
18675

Теперь мы получили list с текстовыми строками, нам нужно преобразовать их в dict и передать в DataFrame.
Здесь json.loads - преобразует текстовые строки в dict.


In [42]:
import json
df = pd.DataFrame(list(map(json.loads, review_lines)))

Теперь мы можем взглянуть, что собой представляют наши данные. DataFrame позволяет их вывести в такой наглядной таблице.


In [72]:
df


Out[72]:
asin helpful overall ... reviewerName summary unixReviewTime
0 0000031887 [0, 0] 5.0 ... Amazon Customer "cameramom" Great tutu- not cheaply made 2011-02-12
1 0000031887 [0, 0] 5.0 ... Amazon Customer Very Cute!! 2013-01-19
2 0000031887 [0, 0] 5.0 ... Carola I have buy more than one 2013-01-04
... ... ... ... ... ... ... ...
18672 B000AYW0OU [1, 1] 4.0 ... D. Gasson Cute Inexpensive Watch 2013-07-08
18673 B000AYW0OU [0, 0] 5.0 ... first time mom Great little watch!! 2011-04-22
18674 B000AYW0OU [0, 0] 4.0 ... Herbert L. Anderson watch 2011-01-19

18675 rows × 9 columns

Данные вначале нашего df


In [73]:
df.head()


Out[73]:
asin helpful overall ... reviewerName summary unixReviewTime
0 0000031887 [0, 0] 5.0 ... Amazon Customer "cameramom" Great tutu- not cheaply made 2011-02-12
1 0000031887 [0, 0] 5.0 ... Amazon Customer Very Cute!! 2013-01-19
2 0000031887 [0, 0] 5.0 ... Carola I have buy more than one 2013-01-04
3 0000031887 [0, 0] 5.0 ... Caromcg Adorable, Sturdy 2014-04-27
4 0000031887 [0, 0] 5.0 ... CJ Grammy's Angels Love it 2014-03-15

5 rows × 9 columns

Данные вконце df


In [75]:
df.tail()


Out[75]:
asin helpful overall ... reviewerName summary unixReviewTime
18670 B000AYW0OU [1, 1] 3.0 ... Cindy "agility dog mom" Band gets dirty fast 2013-07-25
18671 B000AYW0OU [2, 2] 5.0 ... D. Freeman "Mr. Adventurous" Great waterproof watch 2013-06-04
18672 B000AYW0OU [1, 1] 4.0 ... D. Gasson Cute Inexpensive Watch 2013-07-08
18673 B000AYW0OU [0, 0] 5.0 ... first time mom Great little watch!! 2011-04-22
18674 B000AYW0OU [0, 0] 4.0 ... Herbert L. Anderson watch 2011-01-19

5 rows × 9 columns


In [76]:
df.describe()


Out[76]:
overall
count 18675.000000
mean 4.300348
std 1.074955
... ...
50% 5.000000
75% 5.000000
max 5.000000

8 rows × 1 columns

Упражнение: Сохраните и загрузите датасет в разные форматы (CSV, JSON...)


In [61]:
# ваш код здесь, используйте tab для того, чтобы увидеть список доступных для вызова функций

Pandas I/O API это набор высокоуровневых функций, которые можно вызвать как pd.read_csv().

to_csv
to_excel
to_hdf
to_sql
to_json
...


read_csv
read_excel
read_hdf
read_sql
read_json
...

Типы данных Pandas и их преобразование

df.info позволяет нам получить сводную информацию про df: сколько в нем строк, названия и типы столбцов, сколько он занимает памяти...
Мы видим, что столбец unixReviewTime (время, когда ревью было оставленно) имеет тип int64, давайте преобразуем его в datetime64 для более удобной работы с временными данными.


In [66]:
df.info()


<class 'pandas.core.frame.DataFrame'>
RangeIndex: 18675 entries, 0 to 18674
Data columns (total 9 columns):
asin              18675 non-null object
helpful           18675 non-null object
overall           18675 non-null float64
reviewText        18675 non-null object
reviewTime        18675 non-null object
reviewerID        18675 non-null object
reviewerName      18665 non-null object
summary           18675 non-null object
unixReviewTime    18675 non-null datetime64[ns]
dtypes: datetime64[ns](1), float64(1), object(7)
memory usage: 1.3+ MB
Out[66]:
asin helpful overall ... reviewerName summary unixReviewTime
0 0000031887 [0, 0] 5.0 ... Amazon Customer "cameramom" Great tutu- not cheaply made 2011-02-12
1 0000031887 [0, 0] 5.0 ... Amazon Customer Very Cute!! 2013-01-19
2 0000031887 [0, 0] 5.0 ... Carola I have buy more than one 2013-01-04
3 0000031887 [0, 0] 5.0 ... Caromcg Adorable, Sturdy 2014-04-27
4 0000031887 [0, 0] 5.0 ... CJ Grammy's Angels Love it 2014-03-15

5 rows × 9 columns


In [63]:
df['unixReviewTime'] = pd.to_datetime(df['unixReviewTime'], unit='s')
pd.to_datetime?

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


In [64]:
df.info()


<class 'pandas.core.frame.DataFrame'>
RangeIndex: 18675 entries, 0 to 18674
Data columns (total 9 columns):
asin              18675 non-null object
helpful           18675 non-null object
overall           18675 non-null float64
reviewText        18675 non-null object
reviewTime        18675 non-null object
reviewerID        18675 non-null object
reviewerName      18665 non-null object
summary           18675 non-null object
unixReviewTime    18675 non-null datetime64[ns]
dtypes: datetime64[ns](1), float64(1), object(7)
memory usage: 1.3+ MB

Работа с текстовыми данными.

.str accessor

.str accessor - позволяет вызывать методы для работы с текстовыми строками для всего столбца сразу.

Это очень мощная штука, так как она позволяет легко создавать новые features, которые могут как-то описывать ваши данные.


In [82]:
df.summary


Out[82]:
0        Great tutu-  not cheaply made
1                          Very Cute!!
2             I have buy more than one
                     ...              
18672           Cute Inexpensive Watch
18673             Great little watch!!
18674                            watch
Name: summary, dtype: object

Таким простым вызовом мы получаем новый столбец с длинной строки описания товара, который может быть хорошим индикатором для вашей модели.


In [83]:
df.summary.str.len()


Out[83]:
0        29
1        11
2        24
         ..
18672    22
18673    20
18674     5
Name: summary, dtype: int64

Упражнение: Попробуйте использовать разные строковые методы: lower(), upper(), strip()...


In [13]:
# Your code here

Нижний регистр.


In [85]:
df.summary.str.lower()


Out[85]:
0        great tutu-  not cheaply made
1                          very cute!!
2             i have buy more than one
                     ...              
18672           cute inexpensive watch
18673             great little watch!!
18674                            watch
Name: summary, dtype: object

Верхний регистр.


In [15]:
df.summary.str.upper()


Out[15]:
0                             GREAT TUTU-  NOT CHEAPLY MADE
1                                               VERY CUTE!!
2                                  I HAVE BUY MORE THAN ONE
                                ...                        
278674                 CONVENIENT, LIGHTWEIGHT, AND DURABLE
278675                     HOLDS UP WELL IN REAL WORLD TEST
278676    DON'T TRAVEL? STILL WAY TOO USEFUL FOR 'AVERAG...
Name: summary, dtype: object

Поиск строк, которые содержат определенную подстроку или regex


In [86]:
pattern = 'durable'

In [87]:
df.summary.str.contains(pattern)


Out[87]:
0        False
1        False
2        False
         ...  
18672    False
18673    False
18674    False
Name: summary, dtype: bool

Работа с timeseries

.dt accessor

Также как и .str, .dt позволяет вызывать методы для работы с временными данными для всего столбца.

День недели


In [18]:
df.unixReviewTime.dt.dayofweek


Out[18]:
0         5
1         5
2         4
         ..
278674    6
278675    0
278676    0
Name: unixReviewTime, dtype: int64

Неделя в году


In [19]:
df.unixReviewTime.dt.weekofyear


Out[19]:
0          6
1          3
2          1
          ..
278674    23
278675    28
278676    26
Name: unixReviewTime, dtype: int64

Упражнение: Получите столбец с кварталом года, в котором был оставлен отзыв. (qua...)


In [ ]:
# ваш код

Выбор данных

DataFrame имеет очень мощный функционал для поиска необходимых данных.
Таким простым вызовом мы можем выбрать индексы всех строк отзывов, у которых оценка ниже 5.


In [22]:
df.overall < 5


Out[22]:
0         False
1         False
2         False
          ...  
278674    False
278675    False
278676    False
Name: overall, dtype: bool

Передав их как ключ, мы получим сами строки.


In [23]:
df[df.overall < 5]


Out[23]:
asin helpful overall reviewText ... reviewerID reviewerName summary unixReviewTime
5 0000031887 [0, 0] 4.0 I received this today and I'm not a fan of it ... ... A27UF1MSF3DB2 C-Lo "Cynthia" It's ok 2014-03-31
8 0000031887 [0, 0] 4.0 My daughter liked this, and it with her costum... ... A1NJ71X3YPQNQ9 JBerger Good 2013-11-10
15 0000031887 [0, 0] 3.0 My 3yr old loved this tutu skirt in pink! Was ... ... A26A4KKLAVTMCC Moonlight Came apart in 2weeks! 2014-03-20
... ... ... ... ... ... ... ... ... ...
278657 B00KF9180W [4, 5] 4.0 I go walking a lot in all kinds of weather and... ... A1EVV74UQYVKRY K. Groh Great for Winter or Chilly Walks 2014-06-16
278662 B00KF9180W [2, 3] 4.0 Nice material, but not as nice as silk or mer... ... A3UJRNI8UR4871 Wulfstan "wulfstan" Lightweight & useful 2014-06-09
278663 B00KGCLROK [1, 1] 2.0 These were a free sample for review. I was ex... ... A34BZM6S9L7QI4 Candy Cane "Is it just me?" Wanted to love these 2014-06-21

115437 rows × 9 columns

Полученные индексы мы можем передать в метод loc, вторым аргументом он принимает список столбцов, которые мы хотим видеть.


In [24]:
df.loc[df.overall < 5, ['overall', 'reviewText']]


Out[24]:
overall reviewText
5 4.0 I received this today and I'm not a fan of it ...
8 4.0 My daughter liked this, and it with her costum...
15 3.0 My 3yr old loved this tutu skirt in pink! Was ...
... ... ...
278657 4.0 I go walking a lot in all kinds of weather and...
278662 4.0 Nice material, but not as nice as silk or mer...
278663 2.0 These were a free sample for review. I was ex...

115437 rows × 2 columns

Также мы можем передать более сложные условия для выборки, например, здесь мы выбираем отзывы с оценкой 5, содержащие слово awesome и отзывы с оценкой 1, содержащие слово terrible.


In [90]:
df.loc[((df.overall == 5) & (df.reviewText.str.contains('awesome'))) | ((df.overall == 1) & (df.reviewText.str.contains('terrible'))), ['overall', 'reviewText']]


Out[90]:
overall reviewText
110 5.0 I travel 3 weeks a month and carry 2 50 lb bag...
376 5.0 Okay I love everything Converse. I already had...
387 5.0 DONT stop selling the low top of these chuck t...
... ... ...
18139 5.0 Great walking shoes but can still dress up dec...
18575 5.0 I bought this to replace the current orange tr...
18592 5.0 These boots are great. Durable & can be worn a...

204 rows × 2 columns

Упражнение: Выберите строки с оценкой 5, которые были написанны во вторник и содержат слово love в summary.


In [26]:
# Your code here

isin

isin работает по такому принцип: мы ему передаем набор значений, а он выбирает строки, которые им соответствуют.


In [108]:
# возвращает столбец, содержащий количество уникальных значений asin
products = df.asin.value_counts()

In [93]:
products


Out[93]:
B0000C321X    205
B0001ZNZJM    197
B00012O12A    189
             ... 
B0003YDZG4      5
B0007IQFQ8      5
B0000AU0DH      5
Name: asin, dtype: int64

In [106]:
products[0:3].index


Out[106]:
Index(['B0000C321X', 'B0001ZNZJM', 'B00012O12A'], dtype='object')

Выбираем строки, которые содержат топ 3 популярные товары.


In [107]:
df[df.asin.isin(products[0:3].index)]
# df[df.asin.isin(['B0000C321X', 'B0001ZNZJM', 'B00012O12A'])] - даст тот же результат


Out[107]:
asin helpful overall ... reviewerName summary unixReviewTime
2164 B0000C321X [0, 0] 5.0 ... Ab Quality that you expect from Levis 2013-11-07
2165 B0000C321X [0, 0] 5.0 ... A. F. Stevens "avid fan" great fitting jeans 2011-07-24
2166 B0000C321X [0, 0] 5.0 ... Aleksandr Smirnov Midweight denim good for summer 2013-09-24
... ... ... ... ... ... ... ...
5087 B0001ZNZJM [1, 1] 5.0 ... zacket Great all around 2011-12-31
5088 B0001ZNZJM [0, 0] 4.0 ... Zefeng Zhang Size 2013-05-30
5089 B0001ZNZJM [0, 0] 5.0 ... zentriton Great product!! 2014-02-02

591 rows × 9 columns

Упражнение: Выберите отзывы, которые были оставленны в дни, когда было оставленно больше всего отзывов :D


In [109]:
# ваш код

In [32]:
days = df.unixReviewTime.value_counts()

In [33]:
days


Out[33]:
2013-12-26    918
2013-12-31    827
2014-01-08    803
             ... 
2006-11-13      1
2007-11-21      1
2007-01-18      1
Name: unixReviewTime, dtype: int64

In [34]:
df[df.unixReviewTime.isin(days[0:1].index)]


Out[34]:
asin helpful overall reviewText ... reviewerID reviewerName summary unixReviewTime
67 B00001WRHJ [1, 1] 4.0 My grandson is 5 and I gave him this for Chris... ... A15LON8ZV0GJXD Carolyn Sutter Great for costume-loving kid 2013-12-26
424 B000072UMJ [0, 0] 5.0 Unlike what other reviewers said, this shoe is... ... A1EOMH79B2ES81 Neg8iveZero Weird sizing on the tag... 2013-12-26
733 B0000868O9 [0, 0] 5.0 The Bali Flower Underwire Bra is the most worn... ... A1BB7FU96CZK8R d b pressley Bali Bras definitely have flower power for me 2013-12-26
... ... ... ... ... ... ... ... ... ...
275455 B00GCGIMM4 [2, 2] 4.0 But they are a bit small as her toes are barel... ... ADQMQUP274GRQ caroline hartenberger daughter loves the boots 2013-12-26
275775 B00GKC1KC4 [0, 1] 4.0 I ordered this dress based on the size chart a... ... A1P74Y4JKELC32 The Shoe Collector Red Miusol Keyhole Dress 2013-12-26
277312 B00HVWSQG0 [0, 0] 5.0 I wish i could get these in black too! I am l... ... A38CFDLFA780FL Katherine great socks! 2013-12-26

918 rows × 9 columns

Группировка

groupby работает по такому принципу:

  • Таблица делится на группы
  • К каждой группе применяется определенная функция
  • Результаты объединяются
df.groupby( grouper ).agg('mean')

In [110]:
df.groupby('asin')['reviewText'].agg('count').sort_values()


Out[110]:
asin
B0002M0AQK      5
B0000DYNJ3      5
B00081FOXO      5
             ... 
B00012O12A    189
B0001ZNZJM    197
B0000C321X    205
Name: reviewText, dtype: int64

Упражнение: Вычислите среднюю оценку по каждому уникальному продукту.


In [111]:
# ваш код

Упражение: Вычислите среднюю оценку, которую оставил каждый уникальный пользователь.


In [114]:
# ваш код


Out[114]:
<pandas.tseries.resample.TimeGrouper at 0x1164325f8>

pd.Grouper


In [40]:
df.groupby([pd.Grouper(key='unixReviewTime',freq='D')])['reviewerID'].count()


Out[40]:
unixReviewTime
2003-03-29      1
2003-12-12      1
2004-07-17      1
             ... 
2014-07-21    120
2014-07-22    107
2014-07-23      3
Name: reviewerID, dtype: int64

In [41]:
df.groupby([pd.Grouper(key='unixReviewTime',freq='M')])['reviewerID'].count()


Out[41]:
unixReviewTime
2003-03-31        1
2003-12-31        1
2004-07-31        1
              ...  
2014-05-31    14157
2014-06-30    12987
2014-07-31     8858
Name: reviewerID, dtype: int64

Plotting


In [42]:
%matplotlib inline
import seaborn as sns; sns.set()

In [43]:
df.groupby([pd.Grouper(key='unixReviewTime',freq='A')])['reviewerID'].count().plot(figsize=(6,6))


Out[43]:
<matplotlib.axes._subplots.AxesSubplot at 0x119db2d30>

EXERCISE: Plot the number of reviews timeseries by month, year


In [44]:
# Your code here

EXERCISE: Draw two plots to compare average review rating per day of the week between 2013 and 2014


In [45]:
# Your code here

In [47]:
import matplotlib.pyplot as plt

by_weekday = df.groupby([df.unixReviewTime.dt.year,
                              df.unixReviewTime.dt.dayofweek]).mean()
by_weekday.columns.name = None  # remove label for plot

fig, ax = plt.subplots(1, 2, figsize=(16, 6), sharey=True)
by_weekday.loc[2013].plot(title='Average Reviews Rating by Day of Week (2013)', ax=ax[0]);
by_weekday.loc[2014].plot(title='Average Reviews Rating by Day of Week (2014)', ax=ax[1]);
for axi in ax:
    axi.set_xticklabels(['Mon', 'Tues', 'Wed', 'Thurs', 'Fri', 'Sat', 'Sun'])


EXERCISE: Draw two plots to compare number of reviews per day of the month between 2012 and 2013


In [50]:
import matplotlib.pyplot as plt

by_month = df.groupby([df.unixReviewTime.dt.year,
                              df.unixReviewTime.dt.day])['reviewerID'].count()

fig, ax = plt.subplots(1, 2, figsize=(16, 6), sharey=True)
by_month.loc[2012].plot(title='Average Reviews by Month (2012)', ax=ax[0]);
by_month.loc[2013].plot(title='Average Reviews by Month (2013)', ax=ax[1]);


Summary

Learning objectives:

  • Reading and writing data

    pd.DataFrame()
    pd.read_json()
    pd.read_csv()
    df.to_csv()
    df.to_json()
    ...
  • Understading and formatting pandas data types

    df.info()
    df.to_datetime()
    df.to_categoricals()
    ...
  • Working with text data

    .str accessor
    .str.len()
    ...
  • Working with timeseries data

    .dt accessor
    .str.dayofweek
    ...
  • Indexing

    df.loc[]
    df.iloc[]
    .isin()
  • Grouping

    df.groupby(grouper).agg('mean')
    ...