1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/it-ebooks-pyda-2e-zh

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
В этом репозитории не указан файл с открытой лицензией (LICENSE). При использовании обратитесь к конкретному описанию проекта и его зависимостям в коде.
Клонировать/Скачать
14.md 51 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
gitlife-traslator Отправлено 28.11.2024 16:49 0ce2ae9

Глава 14. Анализ данных на примерах

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

Примеры наборов данных можно найти в репозитории Github, как описано в первой главе.

14.1. Данные USA.gov от Bitly

В 2011 году сервис сокращения URL-адресов Bitly сотрудничал с правительственным сайтом США USA.gov, предоставляя анонимные данные, собранные от пользователей, создающих короткие ссылки на .gov или .mil сайты. В 2011 году, помимо данных в реальном времени, можно было загрузить текстовые файлы с часовыми снимками. На момент написания этой книги (2017 год) этот сервис уже был закрыт, но мы сохранили данные для использования в качестве примера в этой книге.

Рассмотрим пример часового снимка, где каждая строка имеет формат JSON (JavaScript Object Notation), который является распространённым форматом данных для веб-приложений. Например, если мы прочитаем только первую строку файла, то увидим следующее:

In [5]: path = 'datasets/bitly_usagov/example.txt'

In [6]: open(path).readline()
Out[6]: '{ "a": "Mozilla\\/5.0 (Windows NT 6.1; WOW64) AppleWebKit\\/535.11
(KHTML, like Gecko) Chrome\\/17.0.963.78 Safari\\/535.11", "c": "US", "nk": 1,
"tz": "America\\/New_York", "gr": "MA", "g": "A6qOVH", "h": "wfLQtf", "l":
"orofrog", "al": "en-US,en;q=0.8", "hh": "1.usa.gov", "r":
"http:\\/\\/www.facebook.com\\/l\\/7AQEFzjSi\\/1.usa.gov\\/wfLQtf", "u":
"http:\\/\\/www.ncbi.nlm.nih.gov\\/pubmed\\/22415991", "t": 1331923247, "hc":
1331822918, "cy": "Danvers", "ll": [ 42.576698, -70.954903 ] }\n'

Python предоставляет встроенные или сторонние модули для преобразования строк JSON в объекты Python Dictionary. Здесь я буду использовать модуль json и его функцию loads для загрузки данных из файла по одной строке за раз:

import json
path = 'datasets/bitly_usagov/example.txt'
records = [json.loads(line) for line in open(path)]

Теперь объект records представляет собой список объектов Python Dictionary:

In [18]: records[0]
Out[18]:
{'a': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko)
Chrome/17.0.963.78 Safari/535.11',
 'al': 'en-US,en;q=0.8',
 'c': 'US',
 'cy': 'Danvers',
 'g': 'A6qOVH',
 'gr': 'MA',
 'h': 'wfLQtf',
 'hc': 1331822918,
 'hh': '1.usa.gov',
 'l': 'orofrog',
 'll': [42.576698, -70.954903],
 'nk': 1,
 'r': 'http://www.facebook.com/l/7AQEFzjSi/1.usa.gov/wfLQtf',
 't': 1331923247,
 'tz': 'America/New_York',
 'u': 'http://www.ncbi.nlm.nih.gov/pubmed/22415991'}

Подсчёт количества появлений часовых поясов с использованием чистого кода Python

Предположим, мы хотим узнать, какой часовой пояс наиболее часто встречается в этом наборе данных (то есть поле tz). Существует множество способов решить эту задачу. Во-первых, мы можем использовать список для извлечения всех часовых поясов:

In [12]: time_zones = [rec['tz'] for rec in records]
---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-12-db4fbd348da9> in <module>()
----> 1 time_zones = [rec['tz'] for rec in records]
<ipython-input-12-db4fbd348da9> in <listcomp>(.0)
----> 1 time_zones = [rec['tz'] for rec in records]
KeyError: 'tz'

О, нет! Оказывается, не все записи содержат поле часового пояса. Это легко исправить, добавив проверку существования поля 'tz' перед извлечением значения:

In [13]: time_zones = [rec['tz'] for rec in records if 'tz' in rec]

In [14]: time_zones[:10]
Out[14]: 
['America/New_York',
 'America/Denver',
 'America/New_York',
 'America/Sao_Paulo',
 'America/New_York',
 'America/New_York',
 'Europe/Warsaw',
 '',
 '',
 '']

Просмотрев первые 10 часовых поясов, мы видим, что некоторые из них не определены (пустые строки). Хотя их можно исключить, пока мы оставим их. Теперь, чтобы подсчитать количество появлений каждого часового пояса, мы представим два метода: один более сложный (использующий только стандартные библиотеки Python), а другой более простой (с использованием pandas). Один из способов подсчёта — это сохранение счётчика в словаре во время перебора часовых поясов:

def get_counts(sequence):
    counts = {}
    for x in sequence:
        if x in counts:
            counts[x] += 1
        else:
            counts[x] = 1
    return counts

Если вы используете более продвинутые инструменты из стандартной библиотеки Python, вы можете написать код более лаконично:

from collections import defaultdict

def get_counts2(sequence):
    counts = defaultdict(int) # значения будут инициализированы до 0
    for x in sequence:
        counts[x] += 1
    return counts

Я поместил логику в функцию для повышения возможности повторного использования. Чтобы применить её к часовым поясам, просто передайте time_zones:

In [17]: counts = get_counts(time_zones)

In [18]: counts['America/New_York']
Out[18]: 1251

In [19]: len(time_zones)
Out[19]: 3440

Чтобы получить первые 10 часовых поясов и их количество, нам нужно использовать некоторые приёмы работы со словарями:

def top_counts(count_dict, n=10):
    value_key_pairs = [(count, tz) for tz, count in count_dict.items()]
    value_key_pairs.sort()
    return value_key_pairs[-n:]

Затем:

In [21]: top_counts(counts)
Out[21]: 
[(33, 'America/Sao_Paulo'),
 (35, 'Europe/Madrid'),
(36, 'Pacific/Honolulu'),
 (37, 'Asia/Tokyo'),
 (74, 'Europe/London'),
 (191, 'America/Denver'),
 (382, 'America/Los_Angeles'),
 (400, 'America/Chicago'),
 (521, ''),
 (1251, 'America/New_York')]

Если вы ищете в стандартной библиотеке Python, вы найдёте класс Counter из модуля collections, который может упростить эту работу:

In [22]: from collections import Counter

In [23]: counts = Counter(time_zones)

In [24]: counts.most_common(10)
Out[24]: **Текст запроса:**

np.where(cframe['a'].str.contains('Windows'), 'Windows', 'Not Windows')

In [48]: cframe['os'][:5]
Out[48]: 
0        Windows
1    Not Windows
2        Windows
3    Not Windows
4        Windows
Name: os, dtype: object

**Перевод текста запроса:**

НП. где (cframe ['a']. str. содержит ('Windows')), 'Windows', 'Не Windows')

В [48]: кадр ['os'] [: 5]
Выход [48]:
0 Windows
1 Не Windows
2 Windows
3 Не Windows
4 Windows
Имя: os, тип данных: объект

**Продолжения перевода нет, так как в запросе отсутствует продолжение исходного текста.**

*Если у вас есть дополнительные вопросы или уточнения по переводу, пожалуйста, дайте мне знать.* **Анализ предпочтений зрителей: какие фильмы больше всего нравятся женщинам и мужчинам**

Чтобы узнать, какие фильмы больше всего нравятся женской аудитории, можно отсортировать данные по столбцу «F» в порядке убывания:

Young Guns (1988) 3.371795 3.425620 Young Guns II (1990) 2.934783 2.904025 Young Sherlock Holmes (1985) 3.514706 3.363344 Zero Effect (1998) 3.864407 3.723140 eXistenZ (1999) 3.098592 3.289086 [1216 rows x 2 columns]


Для того чтобы понять, какой фильм больше всего нравится женской аудитории, а какой — мужской, можно добавить столбец с разницей средних оценок для каждого фильма и отсортировать по нему:
```python
In [87]: mean_ratings['diff'] = mean_ratings['M'] - mean_ratings['F']
Close Shave, A (1995)                               4.644444  4.473795
Wrong Trousers, The (1993)                          4.588235  4.478261
Sunset Blvd. (a.k.a. Sunset Boulevard) (1950)       4.572650  4.464589
Wallace & Gromit: The Best of Aardman Animation...  4.563107  4.385075
Schindler's List (1993)                             4.562602  4.491415
Shawshank Redemption, The (1994)                    4.539075  4.560625
Grand Day Out, A (1992)                             4.537879  4.293255
To Kill a Mockingbird (1962)                        4.536667  4.372611
Creature Comforts (1990)                            4.513889  4.272277
Usual Suspects, The (1995)                          4.513317  4.518248

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

Также можно вычислить стандартное отклонение оценок для каждого фильма, чтобы найти фильм с наибольшим разбросом оценок. 1.259624 Эвита (1996) 1.253631 Билли Мэдисон (1995) 1.249970 Страх и ненависть в Лас-Вегасе (1998) 1.246408 Двухсотлетний человек (1999) 1.245533 Название: рейтинг, тип данных: float64

Возможно, вы уже заметили, что классификация фильмов представлена в виде строк, разделённых вертикальной чертой (|). Если вы хотите провести анализ классификации фильмов, то сначала необходимо преобразовать её в более удобный формат.

14.3. Имена детей в США с 1880 по 2010 год

Американское управление социального обеспечения (SSA) предоставило данные о частоте имён детей с 1880 года до настоящего времени. Хадли Уикхэм (автор многих популярных пакетов R) часто использует эти данные для демонстрации возможностей обработки данных в R.

Нам нужно выполнить некоторую очистку данных, чтобы загрузить этот набор данных. Это создаст следующий DataFrame:

In [4]: names.head(10)
Out[4]:
        name sex  births  year
0       Mary   F    7065  1880
1       Anna   F    2604  1880
2       Emma   F    2003  1880
3  Elizabeth   F    1939  1880
4     Minnie   F    1746  1880
5   Margaret   F    1578  1880
6        Ida   F    1472  1880
7      Alice   F    1414  1880
8     Bertha   F    1320  1880
9      Sarah   F    1288  1880

С этим набором данных можно сделать многое, например:

  • Рассчитать долю имени (вашего собственного или чужого) за каждый год.
  • Вычислить относительный рейтинг определённого имени.
  • Определить самые популярные имена каждого года, а также имена, которые росли или снижались быстрее всего.
  • Проанализировать тенденции имён: гласные, согласные, длина, общее разнообразие, орфографические изменения, начальные и конечные буквы и т. д.
  • Изучить внешние тенденции: библейские имена, имена знаменитостей, демографические изменения и т. п.

Используя инструменты, описанные ранее, все эти аналитические задачи можно легко выполнить, и я объясню некоторые из них.

К моменту написания этой книги SSA разделило базу данных на несколько файлов по годам, предоставляя общее количество рождений для каждой комбинации пола/имени. Эти файлы можно получить в исходном формате здесь: http://www.ssa.gov/oact/babynames/limits.html.

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

Скачайте файл «National data» names.zip, разархивируйте его, и в каталоге вы найдёте набор файлов (например, yob1880.txt). Я использовал команду UNIX head для просмотра первых 10 строк одного из файлов (в Windows вы можете использовать команду more или открыть текстовый редактор напрямую):

In [94]: !head -n 10 datasets/babynames/yob1880.txt
Mary,F,7065
Anna,F,2604
Emma,F,2003
Elizabeth,F,1939
Minnie,F,1746
Margaret,F,1578
Ida,F,1472
Alice,F,1414
Bertha,F,1320
Sarah,F,1288

Поскольку это очень стандартный формат с разделением запятыми, мы можем использовать pandas.read_csv для загрузки его в DataFrame:

In [95]: import pandas as pd

In [96]: names1880 =
pd.read_csv('datasets/babynames/yob1880.txt',
   ....:                         names=['name', 'sex', 'births'])

In [97]: names1880
Out[97]: 
           name sex  births
0          Mary   F    7065
1          Anna   F    2604
2          Emma   F    2003
3     Elizabeth   F    1939
4        Minnie   F    1746
...         ...  ..     ...
1995     Woodie   M       5
1996     Worthy   M       5
1997     Wright   M       5
1998       York   M       5
1999  Zachariah   M       5
[2000 rows x 3 columns]

Эти файлы содержат только имена, которые появились более 5 раз в этом году. Для простоты мы можем использовать столбец sex для группировки и суммирования значений births:

In [98]: names1880.groupby('sex').births.sum()
Out[98]: 
sex
F     90993
M    110493
Name: births, dtype: int64

Так как набор данных разделён на отдельные файлы по годам, первая задача — объединить все данные в один DataFrame и добавить столбец year. Мы можем использовать функцию pandas.concat для достижения этой цели:

years = range(1880, 2011)

pieces = []
columns = ['name', 'sex', 'births']

for year in years:
    path = 'datasets/babynames/yob%d.txt' % year
    frame = pd.read_csv(path, names=columns)

    frame['year'] = year
    pieces.append(frame)

# Concatenate everything into a single DataFrame
names = pd.concat(pieces, ignore_index=True)

Здесь следует обратить внимание на несколько моментов. Во-первых, concat по умолчанию объединяет несколько DataFrames построчно; во-вторых, необходимо указать ignore_index = True, поскольку мы не хотим сохранять исходный номер строки, возвращаемый read_csv. Теперь у нас есть большой DataFrame, содержащий все данные об именах:

In [100]: names
Out[100]: 
              name sex  births  year
0             Mary   F    7065  1880
1             Anna   F    2604  1880
2             Emma   F    2003  1880
3        Elizabeth   F    1939  1880
4           Minnie   F    1746  1880
...            ...  ..     ...   ...
1690779    Zymaire   M       5  2010
1690780     Zyonne   M       5  2010
1690781  Zyquarius   M       5  2010
1690782      Zyran   M       5  2010
1690783      Zzyzx   M       5  2010
[1690784 rows x 4 columns]

Имея эти данные, мы теперь можем использовать groupby или pivot_table для агрегирования данных на уровне year и sex, как показано на рисунке 14-4:

In [101]: total_births = names.pivot_table('births', index='year',
   .....:                                  columns='sex', aggfunc=sum)

In [102]: total_births.tail()
Out[102]: 
sex         F        M
year                  
2006  1896468  2050234
2007  1916888  2069242
2008  1883645  2032310
2009  1827643  1973359
2010  1759010  1898382

In [103]: total_births.plot(title='Total births by sex and year')

Рисунок 14–4 Общее количество рождений по полу и году Ниже мы вставляем столбец prop, чтобы хранить долю количества детей с определённым именем относительно общего числа новорождённых. Значение prop равное 0.02 означает, что среди 100 новорождённых двое получили это имя. Поэтому сначала мы группируем данные по годам и полу, а затем добавляем новый столбец к каждой группе:

def add_prop(group):
    group['prop'] = group.births / group.births.sum()
    return group
names = names.groupby(['year', 'sex']).apply(add_prop)

Теперь полный набор данных имеет следующие столбцы:

In [105]: names
Out[105]: 
              name sex  births  year      prop
0             Mary   F    7065  1880  0.077643
1             Anna   F    2604  1880  0.028618
2             Emma   F    2003  1880  0.022013
3        Elizabeth   F    1939  1880  0.021309
4           Minnie   F    1746  1880  0.019188
...            ...  ..     ...   ...       ...
1690779     Zymaire   M       5  2010  0.000003
1690780     Zyonne   M       5  2010  0.000003
1690781  Zyquarius   M       5  2010  0.000003
1690782      Zyran   M       5  2010  0.000003
1690783      Zzyzx   M       5  2010  0.000003
[1690784 rows x 5 columns]

При выполнении такой групповой обработки обычно следует проводить некоторые проверки эффективности, например, проверять, равна ли сумма всех prop для каждой группы 1:

In [106]: names.groupby(['year', 'sex']).prop.sum()
Out[106]: 
year  sex
1880  F      1.0
      M      1.0
1881  F      1.0
      M      1.0
1882  F      1.0
            ... 
2008  M      1.0
2009  F      1.0
      M      1.0
2010  F      1.0
      M      1.0
Name: prop, Length: 262, dtype: float64

Работа выполнена. Чтобы облегчить дальнейший анализ, мне нужно извлечь подмножество данных: первые 1000 имён для каждой пары sex/year. Это ещё одна групповая операция:

def get_top1000(group):
    return group.sort_values(by='births', ascending=False)[:1000]
grouped = names.groupby(['year', 'sex'])
top1000 = grouped.apply(get_top1000)
# Drop the group index, not needed
top1000.reset_index(inplace=True, drop=True)

Если вам нравится DIY, вы также можете сделать это следующим образом:

pieces = []
for year, group in names.groupby(['year', 'sex']):
    pieces.append(group.sort_values(by='births', ascending=False)[:1000])
top1000 = pd.concat(pieces, ignore_index=True)

Теперь результат данных выглядит так:

In [108]: top1000
Out[108]: 
             name sex  births  year      prop
0            Mary   F    7065  1880  0.077643
1            Anna   F    2604  1880  0.028618
2            Emma   F    2003  1880  0.022013
3       Elizabeth   F    1939  1880  0.021309
4          Minnie   F    1746  1880  0.019188
...           ...  ..     ...   ...       ...
261872     Camilo   M     194  2010  0.000102
261873     Destin   M     194  2010  0.000102
261874     Jaquan   M     194  2010  0.000102
261875     Jaydan   M     194  2010  0.000102
261876     Maxton   M     193  2010  0.000102
[261877 rows x 5 columns]

Следующий этап анализа данных будет сосредоточен на этом наборе top1000 данных.

Анализ тенденций именования

Имея полный набор данных и только что созданный набор top1000, мы можем начать анализировать различные тенденции именования. Сначала разделим первые 1000 имен на две части: мужскую и женскую:

In [109]: boys = top1000[top1000.sex == 'M']

In [110]: girls = top1000[top1000.sex == 'F']

Это два простых временных ряда, которые можно легко построить после небольшого упорядочивания (например, количество детей, названных John и Mary каждый год). Сначала мы создаём сводную таблицу, которая подсчитывает общее количество рождений для каждого имени и года:

In [111]: total_births = top1000.pivot_table('births', index='year',
   .....:                                    columns='name',
   .....:                                    aggfunc=sum)

Теперь мы используем метод plot DataFrame для построения графиков нескольких имён (см. рисунок 14-5):

In [112]: total_births.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 131 entries, 1880 to 2010
Columns: 6868 entries, Aaden to Zuri
dtypes: float64(6868)
memory usage: 6.9 MB

In [113]: subset = total_births[['John', 'Harry', 'Mary', 'Marilyn']]

In [114]: subset.plot(subplots=True, figsize=(12, 10), grid=False,
   .....:             title="Number of births per year")

Рисунок 14-5 Кривые нескольких мужских и женских имён с течением времени

Из графика видно, что эти имена больше не популярны среди американцев. Однако дело обстоит не так просто, и в следующем разделе мы узнаем, как это произошло.

Оценка роста разнообразия имён

Одно из возможных объяснений заключается в том, что родители всё меньше хотят давать своим детям популярные имена. Эту гипотезу можно проверить с помощью данных. Один из способов — рассчитать долю самых популярных 1000 имён и построить график по годам и полам:

In [116]: table = top1000.pivot_table('prop', index='year',
   .....:                             columns='sex', aggfunc=sum)

In [117]: table.plot(title='Sum of table1000.prop by year and sex',
   .....:            yticks=np.linspace(0, 1.2, 13), xticks=range(1880, 2020, 10)
)

Рисунок 14-6 Доля первых 1000 имён в общем количестве рождений по годам и полам

График показывает, что разнообразие имён действительно увеличилось (доля первых 1000 имён снизилась). Другой способ — подсчитать количество различных имён, составляющих половину от общего количества рождений, но этот расчёт сложен. Мы рассмотрим только имена мальчиков 2010 года:

In [118]: df **В приведённом тексте рассматриваются различные методы работы с данными и их анализ.**

В запросе используется язык Python. 

**Текст содержит фрагменты кода, которые оставлены без перевода.**

= boys[boys.year == 2010]

In [119]: df
Out[119]: 
           name sex  births  year      prop
260877    Jacob   M   21875  2010  0.011523
260878    Ethan   M   17866  2010  0.009411
260879  Michael   M   17133  2010  0.009025
260880   Jayden   M   17030  2010  0.008971
260881  William   M   16870  2010  0.008887
...         ...  ..     ...   ...       ...
261872   Camilo   M     194  2010  0.000102
261873   Destin   M     194  2010  0.000102
261874   Jaquan   M     194  2010  0.000102
261875   Jaydan   M     194  2010  0.000102
261876   Maxton   M     193  2010  0.000102
[1000 rows x 5 columns]

После сортировки по prop в порядке убывания мы хотим узнать, сколько имён нужно сложить вместе, чтобы получить 50%. Хотя можно написать цикл for для достижения этой цели, NumPy предлагает более умный векторный способ. Сначала мы вычисляем совокупную сумму prop с помощью метода cumsum(), а затем используем метод searchsorted() для поиска позиции, где 0,5 должен быть вставлен, чтобы сохранить порядок:

In [120]: prop_cumsum = df.sort_values(by='prop', ascending=False).prop.cumsum()

In [121]: prop_cumsum[:10]
Out[121]: 
260877    0.011523
260878    0.020934
260879    0.029959
260880    0.038930
260881    0.047817
260882    0.056579
260883    0.065155
260884    0.073414
260885    0.081528
260886    0.089621
Name: prop, dtype: float64

In [122]: prop_cumsum.values.searchsorted(0.5)
Out[122]: 116

Поскольку массив индексируется начиная с 0, мы должны добавить 1 к этому результату, что даёт нам окончательный результат 117. Для сравнения, если мы возьмём данные за 1900 год, этот номер будет намного меньше:

In [123]: df = boys[boys.year == 1900]

In [124]: in1900 = df.sort_values(by='prop', ascending=False).prop.cumsum()

In [125]: in1900.values.searchsorted(0.5) + 1
Out[125]: 25

Теперь мы можем выполнить этот расчёт для всех комбинаций year/sex. Мы группируем данные по этим полям, а затем применяем функцию для вычисления значения для каждой группы:

def get_quantile_count(group, q=0.5):
    group = group.sort_values(by='prop', ascending=False)
    return group.prop.cumsum().values.searchsorted(q) + 1

diversity = top1000.groupby(['year', 'sex']).apply(get_quantile_count)
diversity = diversity.unstack('sex')

Сейчас diversity — это DataFrame с двумя временными рядами (по одному для каждого пола, проиндексированному по годам). С помощью IPython вы можете просмотреть его содержимое и построить график (например, рисунок 14-7):

In [128]: diversity.head()
Out[128]: 
sex    F   M
year        
1880  38  14
1881  38  14
1882  38  15
1883  39  15
1884  39  16

In [129]: diversity.plot(title="Number of popular names in top 50%")

Из графика видно, что разнообразие женских имён всегда выше, чем мужских, и продолжает расти. Читатели могут сами проанализировать, какие факторы способствуют этому разнообразию (например, изменения в написании).

Изменение последней буквы

В 2007 году исследователь имён Laura Wattenberg на своём веб-сайте отметила (http://www.babynamewizard.com), что распределение мужских имён по последней букве значительно изменилось за последние сто лет. Чтобы понять ситуацию, я сначала объединил все данные о рождении по годам, полу и последней букве:

# extract last letter from name column
get_last_letter = lambda x: x[-1]
last_letters = names.name.map(get_last_letter)
last_letters.name = 'last_letter'

table = names.pivot_table('births', index=last_letters,
                          columns=['sex', 'year'], aggfunc=sum)

Затем я выбрал три репрезентативных года и вывел первые несколько строк:

In [131]: subtable = table.reindex(columns=[1910, 1960, 2010], level='year')

In [132]: subtable.head()
Out[132]: 
sex                 F                            M                    
year             1910      1960      2010     1910      1960      2010
last_letter                                                           
a            108376.0  691247.0  670605.0    977.0    5204.0   28438.0
b                 NaN     694.0     450.0    411.0    3912.0   38859.0
c                 5.0      49.0     946.0    482.0   15476.0   23125.0
d              6750.0    3729.0    2607.0  22111.0  262112.0   44398.0
e            133569.0  435013.0  313833.0  28655.0  178823.0  129012.0

Далее мы нормализуем таблицу, чтобы вычислить долю каждой буквы в общем количестве рождений:

In [133]: subtable.sum()
Out[133]: 
sex  year
F    1910     396416.0
     1960    2022062.0
     2010    1759010.0
M    1910     194198.0
     1960    2132588.0
2010    1898382.0
dtype: float64

In [134]: letter_prop = subtable / subtable.sum()

In [135]: letter_prop
Out[135]: 
sex                 F                             M                    
year             1910      1960      2010      1910      1960      2010
last_letter                                                            
a            0.273390  0.341853  0.381240  0.005031  0.002440  0.014980
b                 NaN  0.000343  0.000256  0.002116  0.001834  0.020470
c            0.000013  0.000024  0.000538 **Имеющиеся данные представляют собой текст на языке Python.**

В запросе содержатся фрагменты кода, которые невозможно перевести без контекста. 

**Текст запроса:**

```python
import matplotlib.pyplot as plt

fig, axes = plt.subplots(2, 1, figsize=(10, 8))
letter_prop['M'].plot(kind='bar', rot=0, ax=axes[0], title='Male')
letter_prop['F'].plot(kind='bar', rot=0, ax=axes[1], title='Female',
                      legend=False)

Перевод:

Импортируем модуль matplotlib.pyplot как plt.

Создаём переменные fig и axes, присваивая им значения, возвращаемые методом subplots() модуля pyplot, который импортировали. Метод принимает три аргумента: количество строк (2), количество столбцов (1), размер фигуры (10 на 8).

Вызываем метод plot() объекта letter_prop с ключом 'M'. В качестве аргументов передаём следующие параметры: тип графика — 'bar', значение поворота — 0, ссылка на ось — axes[0], заголовок — 'Male'.

Вызываем метод plot() объекта letter_prop с ключом 'F'. В качестве аргументов передаём те же параметры, что и для предыдущего вызова метода plot(), за исключением заголовка, который равен 'Female'. Также добавляем параметр legend=False. ``` non-null object dtypes: int64(1), object(3) memory usage: 207.5+ KB

In [173]: col_mapping = {'description' : 'nutrient', .....: 'group' : 'nutgroup'} In [174]: nutrients = nutrients.rename(columns=col_mapping, copy=False)

In [175]: nutrients Out[175]: nutrient nutgroup units value id 0 Protein Composition g 25.180 1008 1 Total lipid (fat) Composition g 29.200 1008 2 Carbohydrate, by difference Composition g 3.060 1008 3 Ash Other g 3.280 1008 4 Energy Energy kcal 376.000 1008 ... ... ... ... ... ... 389350 Vitamin B-12, added Vitamins mcg 0.000 43546 389351 Cholesterol Other mg 0.000 43546 389352 Fatty acids, total saturated Other g 0.072 43546 389353 Fatty acids, total monounsaturated Other g 0.028 43546 389354 Fatty acids, total polyunsaturated Other g 0.041 43546 [375176 rows x 5 columns]


Сделав это, можно объединить данные `info` и `nutrients`:
```python
In [176]: ndata = pd.merge(nutrients, info, on='id', how='outer')

In [177]: ndata.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 375176 entries, 0 to 375175
Data columns (total 8 columns):
nutrient        375176 non-null object
nutgroup        375176 non-null object
units           375176 non-null object
value           375176 non-null float64
id              375176 non-null int64
food            375176 non-null object
fgroup          375176 non-null object
manufacturer    293054 non-null object
dtypes: float64(1), int64(1), object(6)
memory usage: 25.8+ MB

In [178]: ndata.iloc[30000]
Out[178]: 
nutrient                                       Glycine
nutgroup                                   Amino Acids
units                                                g
value                                             0.04
id                                                6158
food            Soup, tomato bisque, canned, condensed
fgroup                      Soups, Sauces, and Gravies
manufacturer                                          
Name: 30000, dtype: object

Теперь мы можем создать диаграмму средних значений (как показано на рисунке 14-11):

In [180]: result = ndata.groupby(['nutrient', 'fgroup'])['value'].quantile(0.5)

In [181]: result['Zinc, Zn'].sort_values().plot(kind='barh')

Рисунок 14–11. Средние значения содержания цинка

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

by_nutrient = ndata.groupby(['nutgroup', 'nutrient'])

get_maximum = lambda x: x.loc[x.value.idxmax()]
get_minimum = lambda x: x.loc[x.value.idxmin()]

max_foods = by_nutrient.apply(get_maximum)[['value', 'food']]

# make the food a little smaller
max_foods.food = max_foods.food.str[:50]

Полученный DataFrame слишком велик, чтобы полностью его здесь привести. Здесь представлен только раздел «Amino Acids»:

In [183]: max_foods.loc['Amino Acids']['food']
Out[183]: 
nutrient
Alanine                          Gelatins, dry powder, unsweetened
Arginine                              Seeds, sesame flour, low-fat
Aspartic acid                                  Soy protein isolate
Cystine               Seeds, cottonseed flour, low fat (glandless)
Glutamic acid                                  Soy protein isolate
                                       ...                        
Serine           Soy protein isolate, PROTEIN TECHNOLOGIES INTE...
Threonine        Soy protein isolate, PROTEIN TECHNOLOGIES INTE...
Tryptophan        Sea lion, Steller, meat with fat (Alaska Native)
Tyrosine         Soy protein isolate, PROTEIN TECHNOLOGIES INTE...
Valine           Soy protein isolate, PROTEIN TECHNOLOGIES INTE...
Name: food, Length: 19, dtype: object

14.5 База данных Федерального избирательного комитета США за 2012 год

Федеральный избирательный комитет США опубликовал данные о финансировании политических кампаний. Эти данные включают в себя информацию об именах спонсоров, их профессиях, работодателях, адресах и суммах взносов. Нас интересуют данные по выборам президента США в 2012 году (http://www.fec.gov/disclosurep/PDownload.do). В июне 2012 года я скачал набор данных размером 150 МБ (P00000001-ALL.csv), который мы загрузим с помощью pandas.read_csv: 23432 ИНФОРМАЦИОННЫЙ ЗАПРОС ОТ BEST EFFORTS 21138 ИНЖЕНЕР 14334 УЧИТЕЛЬ 13990 КОНСУЛЬТАНТ 13273 ПРОФЕССОР 12555 Имя: contbr_occupation, dtype: int64


Не трудно заметить, что многие профессии включают в себя одинаковые основные виды работ, либо существует несколько вариантов одного и того же. Следующий фрагмент кода может очистить некоторые из этих данных (отображение информации о профессии на другую). Обратите внимание, здесь ловко используется dict.get, который позволяет профессиям без отображения также «проходить»:
```python
occ_mapping = {
   'ИНФОРМАЦИОННЫЙ ЗАПРОС BEST EFFORTS' : 'НЕ ПРЕДОСТАВЛЕНО',
   'ИНФОРМАЦИОННЫЙ ЗАПРОС' : 'НЕ ПРЕДОСТАВЛЕНО',
   'ИНФОРМАЦИОННЫЙ ЗАПРОС (BEST EFFORTS)' : 'НЕ ПРЕДОСТАВЛЕНО',
   'CEO': 'CEO'
}

# Если отображение не предоставлено, вернуть x
f = lambda x: occ_mapping.get(x, x)
fec.contbr_occupation = fec.contbr_occupation.map(f)

Я также провёл аналогичную обработку информации о работодателе:

emp_mapping = {
   'ИНФОРМАЦИОННЫЙ ЗАПРОС PER BEST EFFORTS' : 'НЕ ПРЕДОСТАВЛЕНО',
   'ИНФОРМАЦИОННЫЙ ЗАПРОС' : 'НЕ ПРЕДОСТАВЛЕНО',
   'SELF' : 'SELF-EMPLOYED',
   'САМОЗАНЯТЫЙ' : 'САМОЗАНЯТЫЙ',
}

# Если сопоставление не предоставлено, вернуть x
f = lambda x: emp_mapping.get(x, x)
fec.contbr_employer = fec.contbr_employer.map(f)

Теперь вы можете использовать pivot_table для агрегирования данных по партиям и профессиям, а затем отфильтровать данные с общей суммой взносов менее 2 миллионов долларов:

In [201]: by_occupation = fec.pivot_table('contb_receipt_amt',
   .....:                                 index='contbr_occupation',
   .....:                                 columns='party', aggfunc='sum')

In [202]: over_2mm = by_occupation[by_occupation.sum(1) > 2000000]

In [203]: over_2mm
Out[203]: 
party                 Democrat    Republican
contbr_occupation                           
АДВОКАТ           11141982.97  7.477194e+06
CEO                 2074974.79  4.211041e+06
КОНСУЛЬТАНТ          2459912.71  2.544725e+06
ИНЖЕНЕР             951525.55  1.818374e+06
ИСПОЛНИТЕЛЬНЫЙ           1355161.05  4.138850e+06
...                        ...           ...
ПРЕЗИДЕНТ           1878509.95  4.720924e+06
ПРОФЕССОР           2165071.08  2.967027e+05
НЕДВИЖИМОСТЬ          528902.09  1.625902e+06
ВЫШЕЛ НА ПЕНСИЮ            25305116.38  2.356124e+07
САМООБЕСПЕЧИВАЕМЫЙ        672393.40  1.640253e+06
[17 строк x 2 столбца]

Эти данные можно представить в виде гистограммы (barh означает горизонтальную гистограмму, как показано на рисунке 14-12):

In [205]: over_2mm.plot(kind='barh')

Рисунок 14–12: Распределение общих взносов по различным профессиям для каждой партии

Возможно, вы также захотите узнать, какие профессии и предприятия имеют самые высокие общие взносы для Обамы и Ромни. Для этого мы сначала группируем кандидатов, а затем используем метод, аналогичный top, описанному в этой главе:

def get_top_amounts(group, key, n=5):
    totals = group.groupby(key)['contb_receipt_amt'].sum()
    return totals.nlargest(n)

Затем мы агрегируем данные по профессиям и работодателям:

In [207]: grouped = fec_mrbo.groupby('cand_nm')

In [208]: grouped.apply(get_top_amounts, 'contbr_occupation', n=7)
Out[208]: 
cand_nm        contbr_occupation    
Obama, Barack  RETIRED                  25305116.38
               ATTORNEY                 11141982.97
               INFORMATION REQUESTED     4866973.96
               HOMEMAKER                 4248875.80
               PHYSICIAN                 3735124.94
                                           ...     
Romney, Mitt   HOMEMAKER                 8147446.22
               ATTORNEY                  5364718.82
               PRESIDENT                 2491244.89
               EXECUTIVE                 2300947.03
               C.E.O.                    1968386.11
Name: contb_receipt_amt, Length: 14, dtype: float64

In [209]: grouped.apply(get_top_amounts, 'contbr_employer', n=10)
Out[209]: 
cand_nm        contbr_employer      
Obama, Barack  RETIRED                  22694358.85
               SELF-EMPLOYED            17080985.96
               NOT EMPLOYED              8586308.70
               INFORMATION REQUESTED     5053480.37
               HOMEMAKER                 2605408.54
                                           ...     
Romney, Mitt   CREDIT SUISSE              281150.00
               MORGAN STANLEY             267266.00
               GOLDMAN SACH & CO.         238250.00
               BARCLAYS CAPITAL           162750.00
               H.I.G. CAPITAL             139500.00
Name: contb_receipt_amt, Length: 20, dtype: float64
``` **Сейчас можно разделить данные об Обаме и Ромни по именам кандидатов и диапазонам сумм пожертвований, чтобы получить гистограмму:**

```python
In [213]: grouped = fec_mrbo.groupby(['cand_nm', labels])

In [214]: grouped.size().unstack(0)
Out[214]: 
cand_nm              Obama, Barack  Romney, Mitt
contb_receipt_amt                               
(0, 1]                       493.0          77.0
(1, 10]                    40070.0        3681.0
(10, 100]                 372280.0       31853.0
(100, 1000]               153991.0       43357.0
(1000, 10000]              22284.0       26186.0
(10000, 100000]                2.0           1.0
(100000, 1000000]              3.0           NaN
(1000000, 10000000]            4.0           NaN

Из этих данных видно, что Обама получил гораздо больше небольших пожертвований. Можно также суммировать суммы пожертвований в каждом диапазоне и нормализовать их, чтобы визуализировать пропорции пожертвований разных размеров для каждого кандидата (см. рисунок 14-13):

In [216]: bucket_sums = grouped.contb_receipt_amt.sum().unstack(0)

In [217]: normed_sums = bucket_sums.div(bucket_sums.sum(axis=1), axis=0)

In [218]: normed_sums
Out[218]: 
cand_nm              Obama, Barack  Romney, Mitt
contb_receipt_amt                               
(0, 1]                    0.805182      0.194818
(1, 10]                   0.918767      0.081233
(10, 100]                 0.910769      0.089231
(100, 1000]               0.710176      0.289824
(1000, 10000]             0.447326      0.552674
(10000, 100000]           0.823120      0.176880
(100000, 1000000]         1.000000           NaN
(1000000, 10000000]       1.000000           NaN

In [219]: normed_sums[:-2].plot(kind='barh')

Рисунок 14–13: Пропорции пожертвований разного размера для обоих кандидатов.

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

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

Группировка данных по штатам

Группировка данных по кандидату и штату является стандартной операцией:

In [220]: grouped = fec_mrbo.groupby(['cand_nm', 'contbr_st'])

In [221]: totals = grouped.contb_receipt_am.sum().unstack(0).fillna(0)

In [222]: totals = totals[totals.sum(1) > 100000]

In [223]: totals[:10]
Out[223]: 
cand_nm    Obama, Barack  Romney, Mitt
contbr_st                             
AK             281840.15      86204.24
AL             543123.48     527303.51
AR             359247.28     105556.00
AZ            1506476.98    1888436.23
CA           23824984.24   11237636.60
CO            2132429.49    1506714.12
CT            2068291.26    3499475.45
DC            4373538.80    1025137.50
DE             336669.14      82712.00
FL            7318178.58    8338458.81

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

In [224]: percent = totals.div(totals.sum(1), axis=0)

In [225]: percent[:10]
Out[225]: 
cand_nm    Obama, Barack  Romney, Mitt
contbr_st                             
AK              0.765778      0.234222
AL              0.507390      0.492610
AR              0.772902      0.227098
AZ              0.443745      0.556255
CA              0.679498      0.320502
CO              0.585970      0.414030
CT              0.371476      0.628524
DC              0.810113      0.189887
DE              0.802776      0.197224
FL              0.467417      0.532583

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/it-ebooks-pyda-2e-zh.git
git@api.gitlife.ru:oschina-mirror/it-ebooks-pyda-2e-zh.git
oschina-mirror
it-ebooks-pyda-2e-zh
it-ebooks-pyda-2e-zh
master