Рубрики
Фреймворк

ИНТЕРАКТИВНАЯ ВИЗУАЛИЗАЦИЯ ДАННЫХ С ИСПОЛЬЗОВАНИЕМ BOKEH

Bokeh гордится тем, что является библиотекой для интерактивной визуализации данных. В отличие от популярных аналогов в области визуализации Python, таких как Matplotlib и Seaborn, Bokeh отображает свою графику с помощью HTML и JavaScript. Это делает его отличным кандидатом для создания веб-панелей и приложений. Тем не менее, это не менее мощный инструмент для изучения и понимания ваших данных или создания красивых пользовательских диаграмм для проекта, или отчета.

Создание первой фигуры
Прежде чем приступить к работе, нам необходимо, соответственно, установить определенные библиотеки, а именно: pandas, numpy и bokeh. Всего есть несколько способов сделать инфографику в Bokeh:
1.output_file(‘filename.html’) - запишет инфографику в статический HTML-файл.
2.output_notebook() - отобразит инфографику непосредственно в Jupyter Notebook.
Важно отметить, что ни одна из функций не отображает визуализацию. Этого не произойдет, пока не будет вызвана функция show(). Однако они гарантируют, что при вызове show() визуализация появится там, где вы хотите. И так как я буду работать в привычном для меня PyCharm, то я буду использовать первый способ. Давайте же создадим первую фигуру:

from bokeh.io import output_file
from bokeh.plotting import figure, show
# Рисунок будет отображен в статическом HTML-файле
output_file('Создание_первой_фигуры.html',
title='Empty Bokeh Figure')
# Настроить общий объект figure()
fig = figure()
# Посмотрите, как это выглядит
show(fig)
Результат:

Как видите, новое окно браузера открылось с вкладкой под названием Empty Bokeh Figure и пустой фигурой, а сам созданный файл с именем «Создание_первой_фигуры.html» появился в папке с проектом.

Подготовка фигуры к данным
Теперь, когда вы знаете, как создавать и просматривать общую фигуру Bokeh в браузере, пора узнать больше о том, как настроить объект figure(). Объект figure() — это не только основа визуализации данных, но и объект, который открывает все доступные инструменты Bokeh для инфографики. Figure Bokeh является подклассом объекта Bokeh Plot, который предоставляет многие параметры, которые позволяют настраивать эстетические элементы инфографики. Чтобы вы могли получить представление о доступных параметрах настройки, давайте создадим самую уродливую инфографика на свете:

from bokeh.plotting import figure, show
output_file('Подготовка_фигуры_к_данным.html',
title='Bokeh Figure')
fig = figure(background_fill_color='gray',
background_fill_alpha=0.5,
border_fill_color='blue',
border_fill_alpha=0.25,
plot_height=300,
plot_width=500,
x_axis_label='X Label',
x_axis_type='datetime',
x_axis_location='above',
x_range=('2018-01-01', '2018-06-30'),
y_axis_label='Y Label',
y_axis_type='linear',
y_axis_location='left',
y_range=(0, 100),
title='Example Figure',
title_location='right',
toolbar_location='below',
tools='save')
show(fig)
Результат:

Рисование данных с помощью глифов
Пустая фигура не так уж и интересна, поэтому давайте посмотрим на глифы. Глиф — это векторизованная графическая форма или маркер, который используется для представления ваших данных в виде круга или квадрата. Начнем с очень простого примера, нарисовав несколько точек на координатной сетке x-y:

#РИСОВАНИЕ ДАННЫХ С ПОМОЩЬЮ ГЛИФОВ
#Создание первых данных
from bokeh.io import output_file
from bokeh.plotting import figure, show
# Мои данные о координатах x-y
x = [1, 2, 1]
y = [1, 1, 2]
output_file('Рисование_данных_с_помощью_глифов1.html', title='First Glyphs')
# Создайте фигуру без панели инструментов и диапазонов осей
fig = figure(title='My Coordinates',
plot_height=300, plot_width=300,
x_range=(0, 3), y_range=(0, 3),
toolbar_location=None)
# Нарисуйте координаты в виде кругов
fig.circle(x=x, y=y,
color='green', size=10, alpha=0.5)
# Показать сюжет
show(fig)
Результат:

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

#Создание вторых данных
import numpy as np
from bokeh.plotting import figure, show
# Мои данные о подсчете слов
day_num = np.linspace(1, 10, 10)
daily_words = [450, 628, 488, 210, 287, 791, 508, 639, 397, 943]
cumulative_words = np.cumsum(daily_words)
output_file('Рисование_данных_с_помощью_глифов2.html', title='First Glyphs')
# Создаем фигуру с осью x типа datetime
fig = figure(title='My Tutorial Progress',
plot_height=400, plot_width=700,
x_axis_label='Day Number', y_axis_label='Words Written',
x_minor_ticks=2, y_range=(0, 6000),
toolbar_location=None)
# Ежедневные слова будут представлены в виде вертикальных полос (столбцов)
fig.vbar(x=day_num, bottom=0, top=daily_words,
color='blue', width=0.75,
legend='Daily')
# Накопленная сумма будет линией тренда
fig.line(x=day_num, y=cumulative_words,
color='gray', line_width=1,
legend='Cumulative')
# Поместите легенду в левый верхний угол
fig.legend.location = 'top_left'
# Давайте проверим
show(fig)
Результат:

Чтобы объединить столбцы и линии на рисунке, они просто создаются с использованием одного и того же объекта figure (). Кроме того, вы можете увидеть выше, как легко можно создать легенду, задав свойство легенды для каждого глифа. Затем легенда была перемещена в верхний левый угол графика путем присвоения ‘top_left’ значению fig.legend.location.

Небольшая заметка о данных
Каждый раз, когда вы изучаете новую библиотеку визуализации, рекомендуется начинать с некоторых данных из знакомой вам области. Поэтому, в остальных примерах будут использоваться общедоступные данные из Kaggle, который содержит информацию о сезоне 2017-18 Национальной баскетбольной ассоциации (НБА), а именно:
•2017-18_playerBoxScore.csv: снимки статистики игроков по играм;
•2017-18_teamBoxScore.csv: снимки статистики команд по играм;
•2017-18_standings.csv: ежедневное командное положение и рейтинги.
Эти файлы я, соответственно, скачал и разместил в папку с проектом. После чего, они были прочитаны с помощью Pandas DataFrame, используя следующие команды:

import pandas as pd
# Прочитать csv файлы
player_stats = pd.read_csv('2017-18_playerBoxScore.csv', parse_dates=['gmDate'])
team_stats = pd.read_csv('2017-18_teamBoxScore.csv', parse_dates=['gmDate'])
standings = pd.read_csv('2017-18_standings.csv', parse_dates=['stDate'])
print(player_stats)
Этот фрагмент кода считывает данные из трех файлов CSV и автоматически интерпретирует столбцы даты как объекты datetime. Ну и print был применен для того, чтобы проверить, что он выведет, да и считывает ли данные в целом или нет. Результат:

Использование объекта ColumnDataSource
В приведенных выше примерах для представления данных использовались списки Python и массивы Numpy и Bokeh хорошо оснащено для обработки этих типов данных. Однако, когда дело доходит до данных в Python, вы, скорее всего, встретите словари Python и Pandas DataFrames, особенно если вы читаете данные из файла или внешнего источника данных. Bokeh хорошо оснащен для работы с этими более сложными структурами данных и даже имеет встроенные функции для их обработки, а именно ColumnDataSource. Вы можете спросить себя: «Зачем использовать ColumnDataSource, если Bokeh может напрямую взаимодействовать с другими типами данных?» Во-первых, ссылаетесь ли вы напрямую на список, массив, словарь или DataFrame, Bokeh за кулисами все равно превратит его в ColumnDataSource. Что еще более важно, ColumnDataSource значительно упрощает реализацию интерактивных возможностей Bokeh. ColumnDataSource лежит в основе передачи данных глифам, которые вы используете для визуализации.Его основная функция — отображать имена столбцам ваших данных. Это упрощает использование ссылок на элементы данных при построении визуализации. Это также упрощает то же самое для Bokeh при создании визуализации. Давайте начнем с визуализации гонки за первое место в Западной конференции НБА в 2017–2018 годах между действующим чемпионом Golden State Warriors и претендентом Houston Rockets. Ежедневные записи о победах и поражениях этих двух команд хранятся в DataFrame с именем west_top_2:

#ИСПОЛЬЗОВАНИЕ ОБЪЕКТА ColumnDataSource
#Пример 1
west_top_2 = (standings[(standings['teamAbbr'] == 'HOU') | (standings['teamAbbr'] == 'GS')]
.loc[:, ['stDate', 'teamAbbr', 'gameWon']]
.sort_values(['teamAbbr','stDate']))
print(west_top_2.head())
Результат:

Отсюда вы можете загрузить этот DataFrame в два объекта ColumnDataSource и визуализировать гонку:

from bokeh.plotting import figure, show
from bokeh.io import output_file
from bokeh.models import ColumnDataSource
# Output to file
output_file('west-top-2-standings-race1.html',
title='Western Conference Top 2 Teams Wins Race')
# Isolate the data for the Rockets and Warriors
rockets_data = west_top_2[west_top_2['teamAbbr'] == 'HOU']
warriors_data = west_top_2[west_top_2['teamAbbr'] == 'GS']
# Create a ColumnDataSource object for each team
rockets_cds = ColumnDataSource(rockets_data)
warriors_cds = ColumnDataSource(warriors_data)
# Create and configure the figure
fig = figure(x_axis_type='datetime',
plot_height=300, plot_width=600,
title='Western Conference Top 2 Teams Wins Race, 2017-18',
x_axis_label='Date', y_axis_label='Wins',
toolbar_location=None)
# Render the race as step lines
fig.step('stDate', 'gameWon',
color='#CE1141', legend='Rockets',
source=rockets_cds)
fig.step('stDate', 'gameWon',
color='#006BB6', legend='Warriors',
source=warriors_cds)
# Move the legend to the upper left corner
fig.legend.location = 'top_left'
# Show the plot
show(fig)
Результат:

Обратите внимание на ссылки на соответствующие объекты ColumnDataSource при создании двух строк. Вы просто передаете исходные имена столбцов в качестве входных параметров и указываете, какой ColumnDataSource использовать через свойство источника. Визуализация показывает напряженную гонку на протяжении всего сезона, Warriors создают довольно большую подушку в середине сезона. Тем не менее, небольшой спад в конце сезона позволил Rockets наверстать упущенное и в конечном итоге превзойти защищающихся чемпионов и завершить сезон посевным номером один в Западной конференции.
В предыдущем примере были созданы два объекта ColumnDataSource, по одному из подмножества фрейма данных west_top_2. В следующем примере будет воссоздан тот же вывод из одного ColumnDataSource на основе всего west_top_2 с использованием GroupFilter, который создает представление для данных:

#Пример 2
from bokeh.plotting import figure, show
from bokeh.io import output_file
from bokeh.models import ColumnDataSource, CDSView, GroupFilter
# Вывод в файл
output_file('west-top-2-standings-race2.html',
title='Western Conference Top 2 Teams Wins Race')
# Создать ColumnDataSource
west_cds = ColumnDataSource(west_top_2)
# Создайте представления для каждой команды
rockets_view = CDSView(source=west_cds,
filters=[GroupFilter(column_name='teamAbbr', group='HOU')])
warriors_view = CDSView(source=west_cds,
filters=[GroupFilter(column_name='teamAbbr', group='GS')])
# Создаем и настраиваем фигуру
west_fig = figure(x_axis_type='datetime',
plot_height=300, plot_width=600,
title='Western Conference Top 2 Teams Wins Race, 2017-18',
x_axis_label='Date', y_axis_label='Wins',
toolbar_location=None)
# Отрисовываем гонку в виде ступенчатых линий
west_fig.step('stDate', 'gameWon',
source=west_cds, view=rockets_view,
color='#CE1141', legend='Rockets')
west_fig.step('stDate', 'gameWon',
source=west_cds, view=warriors_view,
color='#006BB6', legend='Warriors')
# Переместите легенду в верхний левый угол
west_fig.legend.location = 'top_left'
# Показать сюжет
show(west_fig)
Результат:

Обратите внимание, как GroupFilter передается в CDSView в виде списка. Это позволяет вам комбинировать несколько фильтров вместе, чтобы при необходимости изолировать нужные данные от ColumnDataSource.

Организация нескольких визуализаций с помощью макетов
В турнирной таблице Восточной конференции в Атлантическом дивизионе остались два соперника: Boston Celtics и Toronto Raptors. Прежде чем повторить шаги, использованные для создания west_top_2, давайте попробуем еще раз протестировать ColumnDataSource, используя то, что вы узнали выше. В этом примере вы увидите, как передать весь DataFrame в ColumnDataSource и создать представления для изоляции соответствующих данных:

#ОРГАНИЗАЦИЯ НЕСКОЛЬКИХ ВИЗУАЛИЗАЦИЙ С ПОМОЩЬЮ МАКЕТОВ
from bokeh.plotting import figure, show
from bokeh.io import output_file
from bokeh.models import ColumnDataSource, CDSView, GroupFilter
# Вывод в файл
output_file('Организация_нескольких_визуализаций_с_помощью_макетов.html',
title='Eastern Conference Top 2 Teams Wins Race')
# Создать ColumnDataSource
standings_cds = ColumnDataSource(standings)
# Create views for each team
celtics_view = CDSView(source=standings_cds,
filters=[GroupFilter(column_name='teamAbbr',
group='BOS')])
raptors_view = CDSView(source=standings_cds,
filters=[GroupFilter(column_name='teamAbbr',
group='TOR')])
# Создаем и настраиваем фигуру
east_fig = figure(x_axis_type='datetime',
plot_height=300, plot_width=600,
title='Eastern Conference Top 2 Teams Wins Race, 2017-18',
x_axis_label='Date', y_axis_label='Wins',
toolbar_location=None)
# Отрисовываем гонку в виде ступенчатых линий
east_fig.step('stDate', 'gameWon',
color='#007A33', legend='Celtics',
source=standings_cds, view=celtics_view)
east_fig.step('stDate', 'gameWon',
color='#CE1141', legend='Raptors',
source=standings_cds, view=raptors_view)
# Переместите легенду в верхний левый угол
east_fig.legend.location = 'top_left'
# Показать сюжет
show(east_fig)
Результат:

Добавление взаимодействия
Выбор точек данных
Реализовать поведение выбора так же просто, как добавить несколько конкретных ключевых слов при объявлении ваших глифов. В следующем примере создается диаграмма разброса, которая связывает общее количество попыток трехочковых бросков игроком с их процентом (для игроков, сделавших не менее 100 трехочковых бросков). Данные могут быть агрегированы из DataFrame player_stats:

#ДОБАВЛЕНИЕ ВЗАИМОДЕЙСТВИЯ
#ВЫБОР ТОЧЕК ДАННЫХ
# Найдите игроков, которые сделали хотя бы 1 трехочковый бросок за сезон
three_takers = player_stats[player_stats['play3PA'] > 0]
# Убрать имена игроков, поместив их в один столбец
three_takers['name'] = [f'{p["playFNm"]} {p["playLNm"]}'
for _, p in three_takers.iterrows()]
# Суммируйте общее количество трехочковых попыток и количество попыток для каждого игрока
three_takers = (three_takers.groupby('name')
.sum()
.loc[:,['play3PA', 'play3PM']]
.sort_values('play3PA', ascending=False))
# Отфильтровать всех, кто не сделал хотя бы 100 трехочковых бросков
three_takers = three_takers[three_takers['play3PA'] >= 100].reset_index()
# Добавить столбец с рассчитанным трехбалльным процентом (выполнено / предпринято)
three_takers['pct3PM'] = three_takers['play3PM'] / three_takers['play3PA']
print(three_takers.sample(5))
Результат:

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

# Выбор группы игроков в раздаче из полученного DataFrame, при этом отключить цвет глифов
from bokeh.plotting import figure, show
from bokeh.io import output_file
from bokeh.models import ColumnDataSource, NumeralTickFormatter
# Файл вывода
output_file('Выбор_точек_данных.html',
title='Three-Point Attempts vs. Percentage')
# Сохраним данные в ColumnDataSource
three_takers_cds = ColumnDataSource(three_takers)
# Укажите инструменты выбора, которые будут доступны
select_tools = ['box_select', 'lasso_select', 'poly_select', 'tap', 'reset']
# Создание картинки
fig = figure(plot_height=400,
plot_width=600,
x_axis_label='Three-Point Shots Attempted',
y_axis_label='Percentage Made',
title='3PT Shots Attempted vs. Percentage Made (min. 100 3PA), 2017-18',
toolbar_location='below',
tools=select_tools)
# Отформатируйте метки делений оси Y в процентах
fig.yaxis[0].formatter = NumeralTickFormatter(format='00.0%')
# Добавить квадрат, представляющий каждого игрока
fig.square(x='play3PA',
y='pct3PM',
source=three_takers_cds,
color='royalblue',
selection_color='deepskyblue',
nonselection_color='lightgray',
nonselection_alpha=0.3)
# Показать результат
show(fig)
Сначала укажите инструменты выбора, которые вы хотите сделать доступными. В приведенном выше примере «box_select», «lasso_select», «poly_select» и «tap» (плюс кнопка сброса) были указаны в списке под названием select_tools. Когда фигура создается, панель инструментов располагается «below» («ниже») графика, и список передается в инструменты, чтобы сделать инструменты, выбранные выше, доступными. Каждый игрок изначально представлен синим квадратным глифом, но при выборе игрока или группы игроков устанавливаются следующие конфигурации:
• Превратите выбранных игроков в темно-синий
• Измените глифы всех невыделенных игроков на светло-серый цвет с непрозрачностью 0,3
Результат:

Добавление действий при наведении курсора
Таким образом, реализована возможность выбора конкретных точек данных игроков, которые кажутся интересными на моем графике разброса, но что, если вы хотите быстро увидеть, каких отдельных игроков представляет глиф? Один из вариантов — использовать Bokeh HoverTool (), чтобы показывать всплывающую подсказку, когда курсор пересекает пути с глифом. Все, что вам нужно сделать, это добавить в приведенный выше фрагмент кода следующее:

#ДОБАВЛЕНИЕ ДЕЙСТВИЙ ПРИ НАВЕДЕНИИ КУРСОРА
from bokeh.models import HoverTool
output_file('Добавление_действий_при_наведении_курсора.html',
title='Three-Point Attempts vs. Percentage')
# Отформатируйте всплывающую подсказку
# Отформатируйте всплывающую подсказку
tooltips = [
('Player','@name'),
('Three-Pointers Made', '@play3PM'),
('Three-Pointers Attempted', '@play3PA'),
('Three-Point Percentage','@pct3PM{00.0%}'),
]
# Настроить рендерер, который будет использоваться при наведении
hover_glyph = fig.circle(x='play3PA', y='pct3PM', source=three_takers_cds,
size=15, alpha=0,
hover_fill_color='black', hover_alpha=0.5)
# Добавляем HoverTool к фигуре
fig.add_tools(HoverTool(tooltips=tooltips, renderers=[hover_glyph]))
# Показать
show(fig)
HoverTool() немного отличается от инструментов выбора, которые вы видели выше, тем, что у него есть свойства, в частности всплывающие подсказки. Во-первых, вы можете настроить отформатированную всплывающую подсказку, создав список кортежей, содержащих описание и ссылку на ColumnDataSource. Этот список был передан в качестве входных данных в HoverTool(), а затем просто добавлен к рисунку с помощью add_tools(). Для большего подчеркивания игроков при наведении, был создан новый глиф, в данном случае кружков вместо квадратов, и присвоения ему hover_glyph. Обратите внимание, что начальная непрозрачность установлена на ноль, поэтому она невидима, пока курсор не коснется ее. Свойства, которые появляются при наведении курсора, фиксируются путем установки hover_alpha значения 0,5 вместе с hover_fill_color. Теперь вы увидите, как маленький черный кружок появляется над исходным квадратом при наведении курсора на различные маркеры. Вот что произошло:

Выделение данных с помощью легенды
В этом примере вы увидите две идентичные диаграммы разброса, на которых сравниваются игровые очки и подборы Леброна Джеймса и Кевина Дюранта. Единственная разница будет заключаться в том, что один будет использовать hide в качестве своей click_policy, а другой — mute. Первый шаг — настроить вывод и настроить данные, создав представление для каждого игрока из кадра данных player_stats:

#ВЫДЕЛЕНИЕ ДАННЫХ С ПОМОЩЬЮ ЛЕГЕНДЫ
from bokeh.plotting import figure, show
from bokeh.io import output_file
from bokeh.models import ColumnDataSource, CDSView, GroupFilter
from bokeh.layouts import row
# Вывести в записной книжке
output_file('lebron-vs-durant.html',
title='LeBron James vs. Kevin Durant')
# Хранить данные в ColumnDataSource
player_gm_stats = ColumnDataSource(player_stats)
# Создайте представление для каждого игрока
lebron_filters = [GroupFilter(column_name='playFNm', group='LeBron'),
GroupFilter(column_name='playLNm', group='James')]
lebron_view = CDSView(source=player_gm_stats,
filters=lebron_filters)
durant_filters = [GroupFilter(column_name='playFNm', group='Kevin'),
GroupFilter(column_name='playLNm', group='Durant')]
durant_view = CDSView(source=player_gm_stats,
filters=durant_filters)
Перед созданием рисунков общие параметры рисунка, маркеры и данные могут быть объединены в словари и повторно использованы. Это не только сохраняет избыточность на следующем шаге, но и предоставляет простой способ настроить эти параметры позже, если потребуется:

# Объединить общие аргументы ключевых слов в dicts
common_figure_kwargs = {
'plot_width': 400,
'x_axis_label': 'Points',
'toolbar_location': None,
}
common_circle_kwargs = {
'x': 'playPTS',
'y': 'playTRB',
'source': player_gm_stats,
'size': 12,
'alpha': 0.7,
}
common_lebron_kwargs = {
'view': lebron_view,
'color': '#002859',
'legend': 'LeBron James'
}
common_durant_kwargs = {
'view': durant_view,
'color': '#FFC324',
'legend': 'Kevin Durant'
}
Теперь, когда различные свойства установлены, два графика разброса можно построить гораздо более кратко:

# Создайте две фигуры и нарисуйте данные
hide_fig = figure(**common_figure_kwargs,
title='Click Legend to HIDE Data',
y_axis_label='Rebounds')
hide_fig.circle(**common_circle_kwargs, **common_lebron_kwargs)
hide_fig.circle(**common_circle_kwargs, **common_durant_kwargs)
mute_fig = figure(**common_figure_kwargs, title='Click Legend to MUTE Data')
mute_fig.circle(**common_circle_kwargs, **common_lebron_kwargs,
muted_alpha=0.1)
mute_fig.circle(**common_circle_kwargs, **common_durant_kwargs,
muted_alpha=0.1)
Обратите внимание, что у mute_fig есть дополнительный параметр под названием muted_alpha. Этот параметр управляет непрозрачностью маркеров, когда отключение звука используется как click_policy. Наконец, устанавливается click_policy для каждой фигуры, и они отображаются в горизонтальной конфигурации:

# Добавляем интерактивности в легенду
hide_fig.legend.click_policy = 'hide'
mute_fig.legend.click_policy = 'mute'
# Визуализировать
show(row(hide_fig, mute_fig))
Результат:

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

Рубрики
Фреймворк

ОЧИСТКА ДАННЫХ С ПОМОЩЬЮ PANDAS И NUMPY

Специалисты по обработке данных тратят много времени на очистку наборов данных и приведение их в форму, с которой они могут работать. Фактически, многие специалисты по данным утверждают, что начальные шаги по получению и очистке данных составляют 80% работы. Именно об этом мы и поговорим в данной главе. Но перед этим, нам необходимо установить и импортировать две библиотеки:

import pandas as pd
import numpy as np
Кроме этого, в проекте мы создаем папку Datasets, куда размещаем наши исходные файлы, с которыми будет происходить работа. В моем случае это: BL-Flickr-Images-Book.csv, olympics.csv и university_towns.txt. Проделав все эти действия, можно приступать к работе.

Удаление столбцов в DataFrame
Часто вы обнаруживаете, что не все категории данных в наборе данных вам нужны. Именно поэтому, библиотека Pandas предоставляет удобный способ удаления ненужных столбцов или строк из DataFrame с помощью функции drop(). Давайте посмотрим на простой пример, в котором мы удаляем несколько столбцов из DataFrame. В приведенных ниже примерах мы передаем относительный путь к pd.read_csv, что означает, что все наборы данных находятся в папке с именем Datasets в нашем текущем рабочем каталоге:

df = pd.read_csv('Datasets/BL-Flickr-Images-Book.csv')
print('Вывод загруженного csv файла:')
print(df.head())
Результат:

Сделав вывод нашего csv файла мы видим, что несколько столбцов предствляют собой вспомогательную информацию, которая была бы полезна для библиотеки, но не очень для описания самой книги: Edition Statement, Corporate Author, Corporate Contributors, Former owner, Engraver, Issuance type и Shelfmarks. Эту информацию мы можем удалить следующим образом:

to_drop = ['Edition Statement',
'Corporate Author',
'Corporate Contributors',
'Former owner',
'Engraver',
'Contributors',
'Issuance type',
'Shelfmarks']
df.drop(to_drop, inplace=True, axis=1)
print('Вывод csv файла с удаленными столбцами:')
print(df.head())
Сначала мы определили список, который содержит имена всех столбцов, которые мы хотим удалить. Затем мы вызываем функцию drop() для нашего объекта, передавая параметр inplace как True и параметр оси как 1, что говорит Pandas об изменениях непосредственно в нашем объекте и что он должен искать значения, которые будут отброшены в столбцах объекта. Результат:

Изменение индекса фрейма данных
Индекс Pandas расширяет функциональность массивов NumPy, чтобы обеспечить более гибкое нарезание и маркировку. Во многих случаях полезно использовать однозначное идентифицирующее поле данных в качестве индекса. Давайте заменим существующий индекс в BL-Flickr-Images-Book.csv столбцом Identifier, используя set_index:

df = df.set_index('Identifier')
print(' Замена существующего индекса столбцом Identifier:')
print(df.head())
Результат:

Кроме этого, мы можем получить доступ к каждой записи простым способом с помощью loc[]. Хотя loc[] может не иметь всего этого интуитивно понятного имени, он позволяет нам выполнять индексацию на основе меток, которая представляет собой маркировку строки или записи независимо от ее положения:

print('Получение доступа к каждой записи:')
print(df.loc[206])
Результат:

Другими словами, 206 — это первая метка индекса. Ранее нашим индексом был RangeIndex: целые числа, начинающиеся с 0, аналог встроенного диапазона Python. Передав имя столбца в set_index, мы изменили индекс на значения в Identifier.

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

print('Вывод поля даты публикации для того, чтобы мы могли выполнять вычисления в будущем')
print(df.loc[1905:, 'Date of Publication'].head(10))
Результат:

Как известно, у конкретной книги может быть только одна дата публикации. Поэтому нам необходимо удалить лишние даты в квадратных скобках, преобразовать диапазоны дат в их «дату начала», полностью удалить даты, в которых мы не уверены и преобразовать строку nan в значение NaN NumPy. Для этого мы будем использовать следующее регулярное выражение: regex = r'^(\d{4})'. Данное выражение предназначено для поиска любых четырех цифр в начале строки, чего достаточно для нашего случая. Это необработанная строка, что является стандартной практикой с регулярными выражениями. \d представляет любую цифру, а {4} повторяет это правило четыре раза. Символ ^ соответствует началу строки, а круглые скобки обозначают группу захвата, которая сигнализирует Pandas, что мы хотим извлечь эту часть регулярного выражения. Сам код:

extr = df['Date of Publication'].str.extract(r'^(\d{4})', expand=False)
print('Модернизированные поля даты публикации:')
print(extr.head())
Результат:

Объединение методов str с NumPy для очистки столбцов
Для начала, давайте выведем содержимое столбца Place of Publication:

print('Вывод содержимого столбца Place of Publication')
print(df['Place of Publication'].head(10))
Результат:

Мы видим, что для некоторых строк место публикации окружено другой ненужной информацией. Если бы мы посмотрели на большее количество значений, мы бы увидели, что это справедливо только для некоторых строк, место публикации которых — ‘London’ или ‘Oxford’. Давайте взглянем на две конкретные записи:

print('Вывод информации о двух конкретных записях:')
print(df.loc[4157862])
print(df.loc[4159587])
Результат:

Эти две книги были изданы в одном месте, но одна имеет дефис в названии места, а другая — нет. Чтобы очистить этот столбец за один проход, мы можем использовать str.contains() для получения логической маски. Чистим колонку следующим образом:

pub = df['Place of Publication']
london = pub.str.contains('London')
print('Вывод очищенной колонки:')
print(london[:5])
oxford = pub.str.contains('Oxford')
Далее объединяем их с помощью np.where:

df['Place of Publication'] = np.where(london, 'London',
np.where(oxford, 'Oxford',
pub.str.replace('-', ' ')))
print('Объединение с помощью np.where')
print(df['Place of Publication'].head())
Результат:

Здесь функция np.where вызывается во вложенной структуре с условием, представляющим собой серию логических значений, полученных с помощью str.contains(). Метод contains() работает аналогично встроенному ключевому слову in, используемому для поиска вхождения объекта в итерируемом объекте (или подстроке в строке). Используемая замена — это строка, представляющая желаемое место публикации. Мы также заменяем дефисы пробелом с помощью str.replace() и переназначаем столбец в нашем DataFrame.

Очистка всего набора данных с помощью функции applymap
В определенных ситуациях вы увидите, что «грязь» не локализована в одном столбце, а более разбросана. В некоторых случаях было бы полезно применить настраиваемую функцию к каждой ячейке или элементу DataFrame. Метод Pandas .applymap() похож на метод in-построил функцию map() и просто применяет функцию ко всем элементам в DataFrame. Давайте посмотрим на пример. Мы создадим DataFrame из ранее добавленного в проект файла «university_towns.txt»:

$ head Datasets/univerisity_towns.txt
Alabama[edit]
Auburn (Auburn University)[1]
Florence (University of North Alabama)
Jacksonville (Jacksonville State University)[2]
Livingston (University of West Alabama)[2]
Montevallo (University of Montevallo)[2]
Troy (Troy University)[2]
Tuscaloosa (University of Alabama, Stillman College, Shelton State)[3][4]
Tuskegee (Tuskegee University)[5]
Alaska[edit]
Мы видим, что у нас есть периодические названия штатов, за которыми следуют университетские города в этом штате: StateA TownA1 TownA2 StateB TownB1 TownB2 …. Если мы посмотрим на то, как названия штатов записаны в файле, мы увидим, что все они имеют в них подстрока [edit]. Мы можем воспользоваться этим шаблоном, создав список (state, city) кортежи и обертывание этого списка в DataFrame:

university_towns = []
with open('Datasets/university_towns.txt') as file:
for line in file:
if '[edit]' in line:
state = line
else:
university_towns.append((state, line))
print('Вывод созданного списка, преобразованного в DataFrame:')
print(university_towns[:5])
Результат:

Мы можем обернуть этот список в DataFrame и установить столбцы как «State» и «RegionName». Pandas возьмет каждый элемент в списке и установит State на левое значение, а RegionName — на правое значение:

towns_df = pd.DataFrame(university_towns,
columns=['State', 'RegionName'])
print('Вывод результирующего DataFrame:')
print(towns_df.head())
Результат:

Хотя мы могли бы очистить эти строки в цикле for выше, Pandas упрощает это. Нам нужно только название штата и название города, а все остальное можно удалить. Хотя здесь мы могли бы снова использовать методы Pandas .str(), мы также могли бы использовать applymap() для сопоставления вызываемого Python с каждым элементом DataFrame.

Переименование столбцов и пропуск строк
Часто наборы данных, с которыми вы будете работать, будут иметь либо имена столбцов, которые непросто понять, либо неважную информацию в первых нескольких и/или последних строках, такую как определения терминов в наборе данных или сноски. В этом случае, мы хотели бы переименовать столбцы и пропустить определенные строки, чтобы можно было перейти к необходимой информации с помощью правильных и понятных меток. Чтобы продемонстрировать, как это сделать, давайте сначала взглянем на первые пять строк все также ранее добавленного набора данных olympics.csv:

$ head -n 5 Datasets/olympics.csv
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15
,? Summer,01 !,02 !,03 !,Total,? Winter,01 !,02 !,03 !,Total,? Games,01 !,02 !,03 !,Combined total
Afghanistan (AFG),13,0,0,2,2,0,0,0,0,0,13,0,0,2,2
Algeria (ALG),12,5,2,8,15,3,0,0,0,0,15,5,2,8,15
Argentina (ARG),23,18,24,28,70,18,0,0,0,0,41,18,24,28,70
Теперь мы прочитаем его в DataFrame Pandas:

olympics_df = pd.read_csv('Datasets/olympics.csv')
print('Вывод olympics.csv:')
print(olympics_df.head())
Результат:

Это действительно грязно! Поэтому, мы должны пропустить одну строку и установить заголовок как первую (с нулевым индексом) строку и переименовать столбцы. Для того, чтобы удалить 0-ю строку мы используем:

olympics_df = pd.read_csv('Datasets/olympics.csv', header=1)
print('Вывод olympics.csv без 0 строки:')
print(olympics_df.head())
Результат:

Теперь у нас есть правильная строка, установленная в качестве заголовка, и все ненужные строки удалены. Обратите внимание на то, как Pandas изменил имя столбца, содержащего названия стран, с NaN на Unnamed: 0. Чтобы переименовать столбцы, мы будем использовать метод rename() DataFrame, который позволяет вам изменить метку оси на основе сопоставления (в данном случае dict). Начнем с определения словаря, который сопоставляет текущие имена столбцов (как ключи) с более удобными (значениями словаря):

new_names = {'Unnamed: 0': 'Country',
'? Summer': 'Summer Olympics',
'01 !': 'Gold',
'02 !': 'Silver',
'03 !': 'Bronze',
'? Winter': 'Winter Olympics',
'01 !.1': 'Gold.1',
'02 !.1': 'Silver.1',
'03 !.1': 'Bronze.1',
'? Games': '# Games',
'01 !.2': 'Gold.2',
'02 !.2': 'Silver.2',
'03 !.2': 'Bronze.2'}
Далее вызываем функцию rename() для нашего объекта:

olympics_df.rename(columns=new_names, inplace=True)
Установка inplace в True указывает, что наши изменения будут внесены непосредственно в объект. Результат:

Рубрики
Фреймворк

ОПИСАТЕЛЬНАЯ СТАТИСТИКА НА PYTHON (МЕТРИКИ ОЦЕНКИ КОРРЕЛЯЦИИ ИЛИ ВЗАИМОЗАВИСИМОСТИ)

В предыдущей части мы уже начали разбирать описательную статистику, а именно: центральные метрики и метрики оценки вариативности. В этой же части мы будем говорить той же описательной статистики, но уже разберем корреляции между парами данных. Корреляция – это статистическая взаимосвязь между двумя или более случайными величинами. И есть две статистические меры, которые характеризуют корреляцию между наборами данных — ковариация и коэффициент корреляции. Но прежде, чем нам начать с ними работать, необходимо установить и импортировать нужные библиотеки, а именно: math, statistics, numpy, scipy.stats, pandas и matplotlib.pyplot. После этого, мы формируем два списка Python, которые будем использовать для получения соответствующих массивов NumPy и серии Pandas:

x = list(range(-10, 11))
y = [0, 2, 2, 2, 2, 3, 3, 6, 7, 4, 7, 6, 6, 9, 4, 5, 5, 10, 11, 12, 14]
x_, y_ = np.array(x), np.array(y)
x__, y__ = pd.Series(x_), pd.Series(y_)
Теперь, когда у нас есть исходные данные, можно начать исследовать взаимосвязи между ними.

Ковариации
Выборочная ковариация — это мера, которая количественно определяет силу и направление взаимосвязи между парой переменных:
• Если корреляция положительная, то и ковариация тоже положительная. Более сильное отношение соответствует более высокой ценности ковариации.
• Если корреляция отрицательна, то и ковариация также отрицательна. Более сильное отношение соответствует более низкому (или более высокому абсолютному) значению ковариации.
• Если корреляция слабая, то ковариация близка к нулю.
Процесс расчета ковариации представлен ниже:

#Ковариация
#Расчет ковариации в чистом Python
mean_x, mean_y = sum(x) / n, sum(y) / n
cov_xy = (sum((x[k] - mean_x) * (y[k] - mean_y) for k in range(n))/ (n - 1))
print(f'Расчет ковариации в чистом Python: {cov_xy}')
#Расчет ковариации с помощью NumPy
cov_matrix = np.cov(x_, y_)
print(f'Расчет ковариационной матрицы с помощью NumPy функцией cov(): {cov_matrix}')
print(f'Проверка, что левый элемент ковариационной матрицы — это ковариация x и x или дисперсия x, '
f'а правый элемент — ковариация y и y или дисперсия y: {x_.var(ddof=1)} и {y_.var(ddof=1)}')
cov_xy = cov_matrix[0, 1]
cov_xy2 = cov_matrix[1, 0]
print(f'Проверка, что два других элемента ковариационной матрицы равны '
f'и представляют фактическую ковариацию между x и y: {cov_xy} и {cov_xy2}')
#Расчет ковариации с помощью Pandas
cov_xy = x__.cov(y__)
cov_xy3 = y__.cov(x__)
print(f'Расчет ковариации с помощью Pandas методом .cov(): {cov_xy} и {cov_xy3}')
Первым делом была найдена ковариация в чистом Python, где сначала необходимо найти среднее значение х и у, а затем применить математическую формулу. Но можно применить функцию cov() библиотеки NumPy, которая возвращает ковариационную матрицу, что и было сделано во втором примере. Обратите внимание, cov() имеет необязательные параметры bias (по умолчанию False) и ddof (по умолчанию None). Их значения по умолчанию подходят для получения образца ковариационной матрицы. Верхний левый элемент ковариационной матрицы — это ковариация x и x или дисперсия x. Точно так же нижний правый элемент — y и y или дисперсия y. А два других элемента ковариационной матрицы равны и представляют фактическую ковариацию между x и y. Как проверить и убедиться, что это правда, разобрано в примере. Использовав np.cov() мы получили то же значение ковариации, что и с чистым Python. Помимо этого, можно было использовать метод .cov() библиотеки Pandas, что и было сделано в третьем примере, где для одного объекта Series вызывается .cov() и передает другой объект в качестве первого аргумента.

Коэффициент корреляции
Коэффициент корреляции или коэффициент корреляции Пирсона — произведение, обозначается символом ??. Коэффициент является еще одним показателем корреляции между данными. К нему надо относиться как к стандартизированной ковариации. Вот несколько важных замечаний:
• ? > 0 указывает на положительную корреляцию.
• ? < 0 указывает на отрицательную корреляцию.
• r = 1 является максимально возможным значением ?. Это свидетельство полной линейной зависимости между переменными.
• r = −1 является минимально возможным значением ?. Это свидетельство полного отсутствия линейной зависимости между переменными.
• r ≈ 0 или когда around около нуля, означает, что корреляция между переменными отсутствует.
Процесс расчета коэффициента корреляции представлен ниже:

#Коэффициент корреляции
#Расчет коэффициента корреляции в чистом Python
var_x = sum((item - mean_x)**2 for item in x) / (n - 1)
var_y = sum((item - mean_y)**2 for item in y) / (n - 1)
std_x, std_y = var_x ** 0.5, var_y ** 0.5
r = cov_xy / (std_x * std_y)
print(f'Расчет коэффициента корреляции в чистом Python: {r}')
#Расчет коэффициента корреляции с помощью scipy.stats
r, p = scipy.stats.pearsonr(x_, y_)
print(f'Расчет коэффициента корреляции и p-value, используя функцию pearsonr() в scipy.stats: {r} и {p} ')
scipy.stats.linregress(x_, y_)
print(f'Расчет коэффициента корреляции с помощью scipy.stats.linregress(): {scipy.stats.linregress(x_, y_)}')
result = scipy.stats.linregress(x_, y_)
r = result.rvalue
print(f'Получение доступа к определенным значениям из результата linregress(), включая коэффициент корреляции, используя точечную запись: {r}')
#Расчет коэффициента корреляции с помощью Pandas
r = x__.corr(y__)
r1 = y__.corr(x__)
print(f'Расчет коэффициента корреляции методом .corr() библиотеки Pandas: {r} и {r1}')
В первом примере показано, как рассчитать это коэффициент в чистом Python. Для этого нам понадобятся средние значения (mean_x и mean_y) и стандартные отклонения (std_x, std_y) для наборов данных x и y, а также их ковариация cov_xy. Далее, этот коэффициент и значение p-value (значение, показывающее принимается или отклоняется гипотеза) были рассчитан с помощью функцию pearsonr() в scipy.stats. Первое значение - это коэффициент корреляции между x_ и x_. Второй элемент — это коэффициент корреляции между y_ и y_. Их значения равны 1,0. В третьем примере коэффициент корреляции был найдем с помощью scipy.stats.linregress(). linregress() принимает x_ и y_, вычисляет линейную регрессию и возвращает результаты — наклон и точка пересечения определяют уравнение прямой регрессии, а rvalue — коэффициент корреляции. Чтобы получить доступ к определенным значениям из результата linregress(), включая коэффициент корреляции, необходимо использовать точечную запись, что показано в примере. И в четвертом примере был использован метод .corr() библиотеки Pandas, который вызывается для одного объекта Series и передает другой объект в качестве первого аргумента.

Работа с 2D данными
Axis
В статистике очень часто работают с 2D данными. NumPy и SciPy предоставляют комплексные средства для работы с ними, а Pandas имеет специальный класс DataFrame для обработки 2D данных. И прежде, чем нам работать с ними, их необходимо создать:

#Работа с данными 2D (таблицы)
#Axis
#Создание 2D массива с помощью Numpy
a = np.array(2, 3, 1],[4, 9, 2], [8, 27, 4], [16, 1, 1], [2, 3, 1)
print(f'Вывод, созданного с помощью Numpy, 2D массива: {a}')
Теперь у нас есть набор 2D данных, который мы будем использовать в этом разделе. Можно применять к нему статистические функции и методы Python так же, как к данным 1D, используя или не используя при этом необязательный параметр axis, то есть ось. Ось может принимать любое из следующих значений:
• axis = None — расчет статистики по всем данным в массиве, как в приведенном выше примере. Такое поведение часто используется по умолчанию в NumPy.
• axis = 0 — расчет статистики для каждого столбца массива. Такое поведение часто используется по умолчанию для статистических функций SciPy.
• axis = 1 — расчет статистики для каждой строки массива.
Внизу представлен код, в котором я попробовал использовать к нашему 2D данным статистические функции и методы Python, как с параметром axis, так и без:

#Использование статистических функций и методов Python к 2d массиву с необязательным параметров axis
np.mean(a, axis=0)
a.mean(axis=1)
print(f'Вывод среднего значения 2D массива методом NumPy на оси = 0 и на оси = 1 соответствнно: {np.mean(a, axis=0)} и {a.mean(axis=1)}')
np.median(a, axis=0)
np.median(a, axis=1)
print(f'Вывод медианы 2D массива методом NumPy на оси = 0 и на оси = 1 соответствнно: {np.median(a, axis=0)} и {np.median(a, axis=1)}')
scipy.stats.gmean(a) # Default: axis=0
print(f'Вывод среднего геометрического значения 2D массива функцией SciPy на оси = 0 : {scipy.stats.gmean(a)}')
scipy.stats.gmean(a, axis=None)
print(f'Вывод среднего геометрического значения для всего 2D массива функцией SciPy: {scipy.stats.gmean(a, axis=None)}')
DataFrames
Класс DataFrame является одним из основных типов данных Pandas. С ним очень удобно работать, потому что в нем есть метки для строк и столбцов. Сам код представлен внизу:

#DataFrames
row_names = ['first', 'second', 'third', 'fourth', 'fifth']
col_names = ['A', 'B', 'C']
df = pd.DataFrame(a, index=row_names, columns=col_names)
print(f'Вывод класса DataFrame с ранее созданным 2d массивом: {df}')
df.mean()
df.var()
print(f'Вывод среднего значения и несмещенной дисперсии для всего класса DataFrame с ранее созданным 2d массивом: {df.mean()} и {df.var()}')
df.mean(axis=1)
print(f'Вывод среднего значения класса DataFrame с ранее созданным 2d массивом по оси = 1: {df.mean(axis=1)}')
df['A']
print(f'Пример изоляции класса DataFrame с ранее созданным 2d массивом по столбцу A: {df["A"]}')
df['A'].mean()
print(f'Пример расчета среднего значения класса DataFrame с ранее созданным 2d массивом по столбцу A: {df["A"].mean()}')
Первым делом был создан DataFrame использовав при этом массив a. На практике имена столбцов имеют значение и должны быть описательными. Имена строк иногда указываются автоматически как 0, 1 и т. Д. Вы можете указать их явно с помощью параметра index, хотя вы можете свободно опускать index, если хотите. Методы DataFrame очень похожи на методы Series, хотя их поведение отличается. Если вы вызываете методы статистики Python без аргументов, то DataFrame будет возвращать результаты для каждого столбца. Если же вы хотите получить результаты для каждой строки, просто укажите параметр axis = 1. Также можно изолировать каждый столбец DataFrame и применять к нему соответствующие методы. Процесс применения статистических методов и функций Python к DataFrame представлен в примере.

Визуализация данных
В дополнение к численному описанию, такому как среднее, медиана или дисперсия, можно использовать визуальные методы для представления, описания и обобщения данных. В этом разделе вы узнаете, как представить свои данные визуально, используя библиотеку matplotlib.pyplot, которая у нас уже установлена и импортирована. matplotlib.pyplot — очень удобная и широко используемая библиотека, хотя это не единственная библиотека Python, доступная для этой цели.

Box Plots
Ящик с усами является отличным инструментом для визуального представления описательной статистики данного набора данных. Он может показывать диапазон, межквартильный диапазон, медиану, моду, выбросы и все квартили. Сам код:

#Визуализация данных
#Box Plots
np.random.seed(seed=0)
x = np.random.randn(1000)
y = np.random.randn(100)
z = np.random.randn(10)
fig, ax = plt.subplots()
ax.boxplot((x, y, z), vert=False, showmeans=True, meanline=True,
labels=('x', 'y', 'z'), patch_artist=True,
medianprops={'linewidth': 2, 'color': 'purple'},
meanprops={'linewidth': 2, 'color': 'red'})
plt.show()
Первый оператор инициализирует генератора случайных чисел NumPy с помощью seed(). Поэтому при каждом запуске скрипта могут получаться одинаковые результаты. Не нужно устанавливать начальное значение и, если его не указывать, то каждый раз будут получаться разные результаты. Другие операторы создают три массива NumPy с нормально распределенными псевдослучайными числами. x относится к массиву с 1000 элементами, y имеет 100, а z содержит 10 элементов. Параметры .boxplot():
• х ваши данные.
• vert устанавливает горизонтальную ориентацию графика, когда False. Ориентация по умолчанию - вертикальная.
• showmeans показывает среднее значение ваших данных, когда True.
• meanline представляет среднее в виде линии, когда истина. Представлением по умолчанию является точка.
• labels: метки ваших данных.
• patch_artist определяет, как рисовать график.
• medianprops обозначает свойства линии, представляющей медиану.
• meanprops указывает свойства линии или точки, представляющей среднее значение.
Есть и другие параметры, но их анализ выходит за рамки данного руководства. Вы можете увидеть три сюжета. Каждый из них соответствует одному набору данных (x, y или z) и показывает следующее:
• Среднее значение — это красная пунктирная линия.
• Медиана — это фиолетовая линия.
• Первый квартиль — левый край синего прямоугольника.
• Третий квартиль — это правый край синего прямоугольника.
• Межквартильный диапазон — это длина синего прямоугольника.
• Диапазон — всё слева направо.
• Выбросы — точки слева и справа.
Сюжетная диаграмма может показывать столько информации на одном рисунке!
Результат:

Гистограммы
Гистограмма особенно полезна, когда в наборе данных содержится большое количество уникальных значений. Гистограмма делит значения из отсортированного набора данных на интервалы, также называемые ячейками. Часто все лотки имеют одинаковую ширину, но это не обязательно так. Значения нижней и верхней границ ячейки называются ребрами ячейки. Частота представляет собой одно значение, которое соответствует каждому бину. Это количество элементов набора данных со значениями между краями корзины. По договоренности, все корзины, кроме самой правой, наполовину открыты. Они включают значения, равные нижним границам, но исключают значения, равные верхним границам. Крайняя правая корзина закрыта, так как включает обе границы. Если вы разделите набор данных с ребрами 0, 5, 10 и 15, то есть три элемента:
• Первый и самый левый столбец содержит значения, большие или равные 0 и меньшие 5.
• Второй контейнер содержит значения, большие или равные 5 и меньшие 10.
• Третий и самый правый контейнер содержит значения, большие или равные 10 и меньшие или равные 15.
Сам код:

#Гистограммы
hist, bin_edges = np.histogram(x, bins=10)
fig, ax = plt.subplots()
ax.hist(x, bin_edges, cumulative=False)
ax.set_xlabel('x')
ax.set_ylabel('Frequency')
plt.show()
Функция np.histogram() — это удобный способ получить данные для гистограмм. Он берет массив с вашими данными и количеством (или ребрами) бинов и возвращает два массива NumPy:
• hist содержит частоту или количество элементов, соответствующих каждому бину.
• bin_edges содержит ребра или границы корзины.
Что рассчитывает histogram(), граф .hist() может показать графически. Первый аргумент .hist() — это последовательность с вашими данными. Второй аргумент определяет края бинов. Третий отключает возможность создания гистограммы с накопленными значениями. Результат представлен внизу:

Вы можете видеть края корзины на горизонтальной оси и частоты на вертикальной оси.
При значении аргумента cumulative = True в .hist() можно получить гистограмму с совокупным количеством элементов:

Частота первого и самого левого лотка — это количество элементов в этом лотке. Частота второго бина — это сумма количества элементов в первом и втором бинах. Другие контейнеры отрисовываются аналогично. Наконец, частота последнего и самого правого бина — это общее количество элементов в наборе данных (в данном случае 1000).

Pie Charts круговые диаграммы
Круговые диаграммы представляют данные с небольшим количеством меток и заданными относительными частотами. Они хорошо работают даже с ярлыками, которые нельзя заказать (например, номинальные данные). Круговая диаграмма представляет собой круг, разделенный на несколько частей. Каждый срез соответствует отдельной метке из набора данных и имеет площадь, пропорциональную относительной частоте, связанной с этой меткой. Сам код:

#Pie Charts круговые диаграммы
x, y, z = 128, 256, 1024
fig, ax = plt.subplots()
ax.pie((x, y, z), labels=('x', 'y', 'z'), autopct='%1.1f%%')
plt.show()
Первым делом мы определяем данные, связанные с тремя метками. Далее, Первый аргумент .pie() — данные, а второй — последовательность соответствующих меток. autopct определяет формат относительных частот, показанных на рисунке. Результат:

Круговая диаграмма показывает x как наименьшую часть круга, y как следующую наибольшую, а затем z как самую большую часть. Проценты обозначают относительный размер каждого значения по сравнению с их суммой.

Bar Charts
Гистограммы также иллюстрируют данные, которые соответствуют заданным меткам или дискретным числовым значениям. Они могут показывать пары данных из двух наборов данных. Элементы одного набора - это метки, а соответствующие элементы другого - их частоты. При желании они также могут отображать ошибки, связанные с частотами. Гистограмма показывает параллельные прямоугольники, называемые барами. Каждая полоса соответствует одной метке и имеет высоту, пропорциональную частоте или относительной частоте ее метки. Сам код:

#Bar Charts
x = np.arange(21)
y = np.random.randint(21, size=21)
err = np.random.randn(21)
fig, ax = plt.subplots()
ax.bar(x, y, yerr=err)
ax.set_xlabel('x')
ax.set_ylabel('y')
plt.show()
В данном коде мы использовали np.arange(), чтобы получить x или массив последовательных целых чисел от 0 до 20. Мы использовали это для представления меток. y — это массив равномерно распределенных случайных целых чисел, также от 0 до 20. Этот массив будет представлять частоты. err содержит нормально распределенные числа с плавающей точкой, которые являются ошибками. Эти значения не являются обязательными. Далее создаем гистограмму с помощью .bar(), если вам нужны вертикальные столбцы или .barh(), если вам нужны горизонтальные столбцы. Результат:

X-Y участки
Диаграмма x-y или диаграмма рассеяния представляет пары данных из двух наборов данных. Горизонтальная ось x показывает значения из набора x, а вертикальная ось y показывает соответствующие значения из набора y. При желании вы можете включить линию регрессии и коэффициент корреляции. Код построения представлен внизу:

#X-Y участки
x = np.arange(21)
y = 5 + 2 * x + 2 * np.random.randn(21)
slope, intercept, r, *__ = scipy.stats.linregress(x, y)
line = f'Regression line: y={intercept:.2f}+{slope:.2f}x, r={r:.2f}'
fig, ax = plt.subplots()
ax.plot(x, y, linewidth=0, marker='s', label='Data points')
ax.plot(x, intercept + slope * x, label=line)
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.legend(facecolor='white')
plt.show()
Сначала мы генерируем два набора данных и выполняем линейную регрессию с помощью scipy.stats.linregress().Набор данных x снова является массивом с целыми числами от 0 до 20. y вычисляется как линейная функция x, искаженная некоторым случайным шумом. linregress возвращает несколько значений. Вам понадобится наклон и точка пересечения линии регрессии, а также коэффициент корреляции r. Затем применяем .plot(), чтобы получить график x-y. Результат:

Схемы Зоны активности
Тепловая карта может быть использована для визуального отображения матрицы. Цвета представляют числа или элементы матрицы. Тепловые карты особенно полезны для иллюстрации ковариационных и корреляционных матриц. Вы можете создать тепловую карту для ковариационной матрицы с помощью .imshow():

#Схемы Зоны активности
matrix = np.cov(x, y).round(decimals=2)
fig, ax = plt.subplots()
ax.imshow(matrix)
ax.grid(False)
ax.xaxis.set(ticks=(0, 1), ticklabels=('x', 'y'))
ax.yaxis.set(ticks=(0, 1), ticklabels=('x', 'y'))
ax.set_ylim(1.5, -0.5)
for i in range(2):
for j in range(2):
ax.text(j, i, matrix[i, j], ha='center', va='center', color='w')
plt.show()
Здесь тепловая карта содержит метки «x» и «y», а также числа из ковариационной матрицы. Результат:

Желтое поле представляет самый большой элемент из матрицы 130.34, а фиолетовое соответствует наименьшему элементу 38.5. Синие квадраты между ними связаны со значением 77,8.

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

Рубрики
Фреймворк

ОПИСАТЕЛЬНАЯ СТАТИСТИКА НА PYTHON (ЦЕНТРАЛЬНЫЕ МЕТРИКИ И МЕТРИКИ ОЦЕНКИ ВАРИАТИВНОСТИ)

В данной работе я познакомился с описательной статистикой. Описательная статистика — это описание и интегральные параметры наборов данных. Если говорить о метриках, то в этой части была проработана центральная метрика (которая говорит нам о центрах концентрации данных, таких как среднее, медиана и мода) и метрика вариативности данных (которая говорит о разбросе значений, таких как дисперсия и стандартное отклонение). Так как я работал в PyCharm, то первым делом я зашел в интерпретатор, установил необходимые библиотеки и вызвал их:

import math
import statistics
import numpy as np
import scipy.stats
import pandas as pd
После этого, я создал некоторые исходные данные:

x = [10.0, 2, 2.5, 5, 26.0]
x_with_nan = [10.0, 2, 2.5, math.nan, 5, 26.0]
print(f'Вывод исходных данных, которые содержатся в x:{x}')
print(f'Вывод исходных данных, которые содержатся в x_with_nan:{x_with_nan}')
Теперь у нас есть списки x и x_with_nan. Они почти одинаковы, с той разницей, что x_with_nan содержат nan значение. Важно понимать поведение процедур статистики Python, когда они сталкиваются с нечисловым значением (nan). В науке о данных пропущенные значения являются общими, и вы часто будете заменять их на nan. Теперь создаем объекты np.ndarray и pd.Series, соответствующие x и x_with_nan:

y, y_with_nan = np.array(x), np.array(x_with_nan)
z, z_with_nan = pd.Series(x), pd.Series(x_with_nan)
print(f'Вывод данных, которые содержатся в y и y_with_nan:{y}, {y_with_nan}')
print(f'Вывод данных, которые содержатся в z и в z_with_nan: {z}, {z_with_nan}')
Теперь у нас есть два массива NumPy (y и y_with_nan) и два объекта Series Pandas (z и z_with_nan). Все это — 1D последовательности значений. После формирования исходных данных, приступаем к расчету центральной метрики, а именно среднего значения:

#Рассчет средних значений
mean_1 = sum(x) / len(x)
print(f'Расчет среднего значения, используя sum и len: {mean_1}')
mean_2 = statistics.mean(x)
print(f'Расчет среднего значения, используя встроенные функции статистики Python (statistics.mean(x)): {mean_2}')
mean_3 = statistics.fmean(x)
print(f'Расчет среднего значения, используя встроенные функции статистики Python (statistics.fmean(x)): {mean_3}')
mean_4 = statistics.mean(x_with_nan)
print(f'Расчет среднего значения, который содержит значения nan, используя встроенные функции статистики Python (statistics.mean(x)): {mean_4}')
mean_5 = np.mean(y)
print(f'Расчет среднего значения, используя NumPy: {mean_5}')
np.nanmean(y_with_nan)
print(f'Расчет среднего значения с помощью NumPy, игнорируя nan: {np.nanmean(y_with_nan)}')
mean_6 = z.mean()
print(f'Расчет среднего значения объекта pd.Series: {mean_6}')
Первое среднее значение было рассчитано на чистом Python, используя sum() и len(), без импорта библиотек. Хотя это чисто и элегантно, но для расчета второго, третьего и четвертого значения были применены встроенные функции библиотеки statistics Python. При расчете пятого среднего значения была использована библиотека NumPy и функция np.mean. А шестое среднее значение было рассчитано с помощью метода .mean() библиотеки Pandas.
Далее, было рассчитано средневзвешенное значение. Средневзвешенное или также называемое средневзвешенным арифметическим или средневзвешенным значением, является обобщением среднего арифметического, которое позволяет вам определить относительный вклад каждой точки данных в результат. Сам расчет:

#Рассчет средневзвешанных значений
x = [6.0, 1, 2.5, 6, 25.0]
w = [0.1, 0.2, 0.3, 0.25, 0.15]
wmean = sum(w[i] * x[i] for i in range(len(x))) / sum(w)
print(f'Расчет средневзвешанного с помощью range: {wmean}')
wmean2 = sum(x_ * w_ for (x_, w_) in zip(x, w)) / sum(w)
print(f'Расчет средневзвешанного с помощью zip: {wmean2}')
y, z, w = np.array(x), pd.Series(x), np.array(w)
wmean3= np.average(y, weights=w)
print(f'Расчет средневзвешанного с помощью np.average для массивово NumPy или серии Pandas: {wmean3}')
o = (w * y).sum() / w.sum()
print(f'Расчет средневзвешанного с помощью поэлементного умножения w * y: {o}')
w = np.array([0.1, 0.2, 0.3, 0.0, 0.2, 0.1])
print(f'Расчет средневзвешанного для набора, который содержит nan : {(w * y_with_nan).sum() / w.sum()}')
Первое и второе средневзвешенное было рассчитано В чистом Python используя комбинацию sum() с range() и zip(). Опять же, это чистая и элегантная реализация, в которой вам не нужно импортировать какие-либо библиотеки. Однако, если у вас большие наборы данных, то NumPy, вероятно, будет лучшим решением. Можно использовать np.average(), как это сделано при расчете третьего показателя, для массивов NumPy или серии Pandas. Для расчета четвертого и пятого показателя, было использовано поэлементное умножение с методом .sum().
После этого, было рассчитано гармоническое среднее, что есть обратная величина от среднего значения обратных величин всех элементов в наборе данных:

#Гармоническое среднее
hmean = len(x) / sum(1 / item for item in x)
print(f'Расчет гармонического среднего: {hmean}')
hmean2 = statistics.harmonic_mean(x)
print(f'Расчет гармонического среднего с помощью statistics.harmonic_mean(): {hmean2}')
statistics.harmonic_mean(x_with_nan)
print(f'Расчет гармонического среднего, где есть nan: {statistics.harmonic_mean(x_with_nan)}')
statistics.harmonic_mean([1, 0, 2])
print(f'Расчет гармонического среднего, где есть 0: {statistics.harmonic_mean([1, 0, 2])}')
scipy.stats.hmean(y)
print(f'Расчет гармонического среднего с помощью scipy.stats.hmean(): {scipy.stats.hmean(y)}')
Как обычно, первое значение было рассчитано на чистом Python. Второе, третье и четвертое значение было рассчитано с помощью функции statistics.harmonic_mean(). И последнее было рассчитано используя scipy.stats.hmean.
Следом был рассчитан среднее геометрическое:

#Среднее геометрическое
gmean = 1
for item in x:
gmean *= item
gmean **= 1 / len(x)
print(f'Вычисление геометрического среднего: {gmean}')
gmean2 = statistics.geometric_mean(x)
print(f'Вычисление геометрического среднего с помощью statistics.geometric_mean(): {gmean2}')
gmean3 = statistics.geometric_mean(x_with_nan)
print(f'Вычисление геометрического среднего где есть nan: {gmean3}')
scipy.stats.gmean(y)
print(f'Вычисление геометрического среднего с помощью scipy.stats.gmean(): {scipy.stats.gmean(y)}')
Первое значение было рассчитано на чистом Python. Второе и третье значение было рассчитано с помощью функции statistics.geometric_mean(). И последнее было рассчитано используя scipy.stats.gmean.
Медиана — это средний элемент отсортированного набора данных. Расчет медианы представлен внизу:

n = len(x)
if n % 2:
median_ = sorted(x)[round(0.5*(n-1))]
else:
x_ord, index = sorted(x), round(0.5 * n)
median_ = 0.5 * (x_ord[index-1] + x_ord[index])
print(f'Расчет медианы: {median_}')
median_2 = statistics.median(x)
print(f'Расчет медианы с помощью statistics.median(): {median_2}')
statistics.median_low(x[:-1])
print(f'Расчет медианы с помощью statistics.median_low: {statistics.median_low(x[:-1])}')
statistics.median_high(x[:-1])
print(f'Расчет медианы с помощью statistics.median_high {statistics.median_high(x[:-1])}')
median_2 = np.median(y)
print(f'Расчет медианы с помощью np.median: {median_2}')
Первое значение было рассчитано на чистом Python. Следующие три были найдены используя statistics.median, при этом, median_low() возвращает меньшее, а median_high() — большее среднее значение. И последняя была найдена с помощью NumPy и функции np.median().
Мода — это значение в наборе данных, которое встречается чаще всего. Если такого значения не существует, набор является мультимодальным, поскольку он имеет несколько модальных значений. Расчет моды представлен внизу:

u = [2, 3, 2, 8, 12]
mode_ = max((u.count(item), item) for item in set(u))[1]
print(f'Вычисление моды: {mode_}')
mode_2 = statistics.mode(u)
print(f'Вычисление моды с помощью statistics.mode(): {mode_2}')
mode_3 = statistics.multimode(u)
print(f'Вычисление моды с помощью statistics.multimode(): {mode_3}')
mode_4 = scipy.stats.mode(u)
print(f'Вычисление моды с помощью scipy.stats.mode(): {mode_4}')
Первое значение, как обычно, получено используя чистый Python. Вы используете u.count(), чтобы получить количество вхождений каждого элемента в u. Элемент с максимальным количеством вхождений — это мода. Обратите внимание, что вам не нужно использовать set(u). Вместо этого вы можете заменить его просто на u и повторить весь список. Второе и третье значение было вычислено с помощью statistics.mode() и statistics.multimode(). Обратите внимание, mode() вернула одно значение, а multimode() в результате вернула список. Однако, это не единственное различие между двумя функциями. Если существует более одного модального значения, то mode() вызывает StatisticsError, а multimode() возвращает список со всеми режимами. И последнее значение было найдено с помощью функции, которая возвращает объект с модальным значением и количество его повторений в наборе данных.
Центральных метрик недостаточно для описания данных. Практически всегда необходимы метрики оценки вариативности данных, которые количественно определяют разброс точек данных. И первым показателем метрики оценки вариативности данных была дисперсия. Дисперсия количественно определяет разброс данных. Численно показывает, как далеко точки данных от среднего значения. Сам расчет:

n = len(x)
mean = sum(x) / n
var_ = sum((item - mean)**2 for item in x) / (n - 1)
print(f'Оценка дисперсии на чистом Python: {var_}')
var_1= statistics.variance(x)
print(f'Оценка дисперсии с помощью statistics.variance(): {var_1}')
statistics.variance(x_with_nan)
print(f'Оценка дисперсии с помощью statistics.variance(), где есть nan: {statistics.variance(x_with_nan)}')
var_2 = np.var(y, ddof=1)
print(f'Оценка дисперсии, используя NumPy с помощью np.var(): {var_2}')
var_3 = y.var(ddof=1)
print(f'Оценка дисперсии, используя NumPy с помощью метода .var(): {var_3}')
Первый метод расчета – используя чистый Python. В целом, этого достаточно и можно правильно дать оценку дисперсии. Однако, более короткое и элегантное решение — использовать функцию statistics.variance() (как сделано это при расчете второго показателя). В результате мы получили тот же результат для дисперсии, что и в первом. И оставшиеся последние два показателя были рассчитаны используя NumPy, а именно функции np.var() и метода .var()
Далее, было рассчитано среднеквадратичное отклонение. Стандартное отклонение выборки является еще одним показателем разброса данных. Он связан с оценкой дисперсией, поскольку стандартное отклонение есть положительным квадратный корень из оценки дисперсии. Стандартное отклонение часто более удобно, чем дисперсия, потому что имеет ту же размерность, что и данные. Сам расчет:

#Среднеквадратичное отклонение
std_ = var_ ** 0.5
print(f'Расчет среднеквадратичного отклонения на чистом Python: {std_}')
std_2 = statistics.stdev(x)
print(f'Расчет среднеквадратичного отклонения с помощью statistics.stdev(): {std_2}')
np.std(y, ddof=1)
print(f'Расчет среднеквадратичного отклонения с помощью NumPy: {np.std(y, ddof=1)}')
После этого, было найдено смещение:

#Смещение
x = [8.0, 1, 2.5, 4, 28.0]
n = len(x)
mean_ = sum(x) / n
var_ = sum((item - mean_)**2 for item in x) / (n - 1)
std_ = var_ ** 0.5
skew_ = (sum((item - mean_)**3 for item in x)
* n / ((n - 1) * (n - 2) * std_**3))
print(f'Расчет смещения на чистом Python: {skew_}')
z, z_with_nan = pd.Series(x), pd.Series(x_with_nan)
print(f'Расчет смещения с помощью Pandas: {z.skew()}')
Первый показатель, был найден, соответственно с помощью чистого Python, а второй с помощью Pandas, используя метод .skew().
Процентиль — такой элемент в наборе данных, так что p элементов в наборе данных меньше или равно его значению. Кроме того, (100 - p) элементов больше или равно этому значению. Если в наборе данных есть два таких элемента, то процентиль является их средним арифметическим. Расчет процентиля представлен внизу:

#Процентили
x = [-5.0, -1.1, 0.1, 2.0, 8.0, 12.8, 21.0, 25.8, 41.0]
print(f'Расчет процентилей с помощью statistics.quantiles(): {statistics.quantiles(x, n=2)}')
statistics.quantiles(x, n=4, method='inclusive')
print(f"Расчет процентилей с помощью statistics.quantiles(): {statistics.quantiles(x, n=4, method='inclusive')}")
y = np.array(x)
np.percentile(y, 5)
print(f'Нахождение 5 процентиля : {np.percentile(y, 5)}')
np.percentile(y, 95)
print(f'Нахождение 95 процентиля : {np.percentile(y, 95)}')
z, z_with_nan = pd.Series(y), pd.Series(y_with_nan)
z.quantile(0.05)
print(f'Нахождение процентиля используя метод .quantile(): {z.quantile(0.05)}')
Первый показатель был найден с помощью statistics.quantiles.В этом примере 8,0 — медиана x, а 0,1 и 21,0 — это 25-й и 75-й процентили выборки соответственно. Параметр n определяет количество результирующих процентилей с равной вероятностью, а метод определяет, как их вычислять. Следующие показатели, а именно 5 и 95 процентили, были найдены с помощью библиотеки NumPy, функции np.percentile(). И последний показатель был найдет используя метод .quantile().
Диапазон данных — это разница между максимальным и минимальным элементом в наборе данных. Эти показатели я нашел используя функцию np.ptp():

#Диапазон
np.ptp(y)
np.ptp(z)
np.ptp(y_with_nan)
np.ptp(z_with_nan)
print(f'Нахождение диапазона с помощью функции np.ptp(): {np.ptp(y),np.ptp(z),np.ptp(y_with_nan),np.ptp(z_with_nan)}')
И последнее, что я искал – сводка описательной статистики:

#Сводка описательной статистики
result = scipy.stats.describe(y, ddof=1, bias=False)
print(f'Сводка описательной статистики с помощью scipy.stats.describe(): {result}')
result2 = z.describe()
print(f'Сводка описательной статистики с помощью метода .describe() в Pandas: {result2}')
Первый показатель был найден с помощью scipy.stats.describe(). В качестве первого аргумента необходимо передать набор данных, который может быть представлен массивом NumPy, списком, кортежем или любой другой подобной структурой данных. Можно опустить ddof = 1, так как это значение по умолчанию и имеет значение только при расчете дисперсии. Указано bias = False для принудительного исправления асимметрии и эксцесса статистического смещения.
description() возвращает объект, который содержит следующую описательную статистику:
• nobs — количество наблюдений или элементов в вашем наборе данных;
• minmax — кортеж с минимальными и максимальными значениями;
• mean — среднее значение;
• variance — дисперсия;
• skewness — асимметрия;
• kurtosis — эксцесс вашего набора данных.
Второй показатель был найден с помощью метода .describe() библиотеки Pandas.

Рубрики
Фреймворк

Автоматизация индивидуальной нарезки файлов Excel и рассылка их на eMail

Автоматизация индивидуальной нарезки файлов Excel и их распространение с помощью Pandas и Outlook
Давайте представим ситуацию, что у нас есть Excel файл, в котором находится обширное количество данных. И эти данные необходимо разделить по отдельным файлам, а затем отправить по электронной почте. Чтобы было более понятно, например, у меня есть файл, в котором я храню данные о клиентах. И по каждому клиенту мне нужно создать собственный файл, а затем отправить ему это по электронной почте. Пример файла изображен внизу:

Если проводить эту операцию вручную, то придется потратить достаточное количество своего времени, а если файл будет содержать информацию не о 10 клиентах, как в моем случае, а о 1000, то это уже будет огромной проблемой. Но ее можно будет решить с помощью Python. Первым делом, как всегда, необходимо выполнить импорт библиотек (некоторые из них необходимо будет установить через pip):

import datetime
import os
import shutil
from pathlib import Path
import pandas as pd
import win32com.client as win32
Далее, в папке с проектом мы создаем несколько папок, а именно: archive и data, в которую кладем файл с исходными данными, в моем случае – customers.xlsx и в ней же создаем папку attachments, в которую у нас будут сохраняться наши новые файлы. После этого пишем следующий код:

## Установить формат даты
today_string = datetime.datetime.today().strftime('%m%d%Y_%I%p')
today_string2 = datetime.datetime.today().strftime('%b %d, %Y')
## Установка имен папок для вложений и архивирования
attachment_path = Path.cwd() / 'data' / 'attachments'
archive_dir = Path.cwd() / 'archive'
src_file = Path.cwd() / 'data' / 'customers.xlsx'
Давайте посмотрим на основной файл данных, который нужно обработать и который является источников множества файлов для клиентов:
df = pd.read_excel(src_file)
df.head()
Следующий шаг — сгруппировать все транзакции, относящиеся к клиентам по их CUSTOMER_ID, вместе. Начнем с создания группы по CUSTOMER_ID:

customer_group = df.groupby('CUSTOMER_ID')
for ID, group_df in customer_group:
print(ID)
Результат:

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

## Запишите каждый идентификатор, группу в отдельные файлы Excel и используйте идентификатор,
## чтобы назвать каждый файл с сегодняшней датой
attachments = []
for ID, group_df in customer_group:
attachment = attachment_path / f'{ID}_{today_string}.xlsx'
group_df.to_excel(attachment, index=False)
attachments.append((ID, str(attachment)))
df2 = pd.DataFrame(attachments, columns=['CUSTOMER_ID', 'FILE'])
Результат:

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

# Отправка индивидуальных отчетов по электронной почте соответствующим получателям
class EmailsSender:
def __init__(self):
self.outlook = win32.Dispatch('outlook.application')
def send_email(self, to_email_address, attachment_path):
mail = self.outlook.CreateItem(0)
mail.To = to_email_address
mail.Subject = today_string2 + ' Report'
mail.Body = """Please find today's report attached."""
mail.Attachments.Add(Source=attachment_path)
# Показать электронную почту
mail.Display(True)
# Отправка
mail.Send()
Также, можно использовать класс EmailsSender, который позволит создавать электронные письма и прикреплять файл Excel:

email_sender = EmailsSender()
for index, row in combined.iterrows():
email_sender.send_email(row['EMAIL'], row['FILE'])
Результат:

Ну и завершающим этапом будет перемещение всех этих созданных файлов в архивный каталог:

# Переместить файлы в архив
for f in attachments:
shutil.move(f[1], archive_dir)
Благодаря данной программе, мы смогли автоматизировать достаточно болезненную ручную работу и сэкономить просто огромное количество своего времени и сил.

Рубрики
Фреймворк

РАБОТА С PDF-ФАЙЛАМИ В PYTHON

В данной работе мы поработаем с PDF файлами используя Python, а именно: мы рассмотрим, как извлекать текст, изображения, разделять на страницы, найти все страницы файла, где имеется нужный нам текст, как вставлять изображения в этот файл, удалять страницы, разделять страницы на четные и нечетные. Давайте же начнем!

Извлечение текста с помощью PyPDF2 и PyMuPDF
Сначала сделаем извлечение текста двумя методами. Первый – используя библиотеку PyPDF2, а второй – PyMuPDF. Что это вообще за библиотеки? PyPDF2 – это библиотека для извлечения информации и содержимого документов, постраничного разделения документов, объединения документов, обрезки страниц и добавления водяных знаков. А PyMuPDF (известный как fitz) - привязка Python для MuPDF, который является облегченным средством просмотра PDF и XPS. Именно поэтому, первым делом мы устанавливаем эти библиотеки: pip3 install pypdf2, pip3 install pymupdf. Далее, в папке с проектом мы создаем еще три дополнительные папки: images, source и dist. Папки images и dist будем использовать для записи результатов работы своих программ, а в папке source храним исходные PDF файлы (которые надо будет заранее туда положить), сами скрипты будем хранить в корне. После всех этих действий, приступаем к извлечению текста с помощью PyPDF2:

from PyPDF2 import PdfFileReader
pdf_document = "source/YourFile.pdf"
with open(pdf_document, "rb") as filehandle:
pdf = PdfFileReader(filehandle)

info = pdf.getDocumentInfo()
pages = pdf.getNumPages()
print("Количество страниц в документе: %i\n\n" % pages)
print("Мета-описание: ", info)
for i in range(pages):
page = pdf.getPage(i)
print("Стр.", i, " мета: ", page, "\n\nСодержание;\n")
print(page.extractText())
В данном коде мы импортируем PdfFileReader, помня о том, что пакет уже установлен. Задаём имя файла из папки source, открывает документ и получаем информацию о документе, используя метод getDocumentInfo() и общее количество страниц getNumPages(). Далее в цикле for читаем каждую страницу, получаем содержимое page.extractText() и печатаем в stdout. Обратите внимание, что PyPDF2 начинает считать страницы с 0, и поэтому вызов pdf.getPage(i) при i = 0 извлекает первую страницу документа. Результат:

Если использовать библиотеку PyMuPDF, то код выполняется аналогично предыдущему методу, единственный момент заключается в том, что импортируемый модуль имеет имя fitz, что соответствует имени PyMuPDF в ранних версиях:

import fitz
pdf_document = "./source/ YourFile.pdf "
doc = fitz.open(pdf_document)
print("Исходный документ: ", doc)
print("\nКоличество страниц: %i\n\n------------------\n\n" % doc.pageCount)
print(doc.metadata)
for current_page in range(len(doc)):
page = doc.loadPage(current_page)
page_text = page.getText("text")
print("Стр. ", current_page+1, "\n\nСодержание;\n")
print(page_text)
Приятной особенностью PyMuPDF является то, что он сохраняет исходную структуру документа без изменений — целые абзацы с разрывами строк сохраняются такими же, как в PDF документе. Результат:

Извлечение изображений из PDF с помощью PyMuPDF
Переходим к изображениям. PyMuPDF упрощает извлечение изображений из документов PDF с использованием метода getPageImageList(). Скрипт, приведённый ниже, основан на примере из вики-страницы PyMuPDF и извлекает и постранично сохраняет все изображения из PDF в формате PNG. Если изображение имеет цветовое пространство CMYK, оно будет сначала преобразовано в RGB. При этом, все извлеченные изображения будут сохраняться у нас в папку images. Сам код:

import fitz
pdf_document = "source/ YourFile.pdf "
doc = fitz.open(pdf_document)
print("Исходный документ", doc)
print("\nКоличество страниц: %i\n\n------------------\n\n" % doc.pageCount)
print(doc.metadata)
page_count = 0
for i in range(len(doc)):
for img in doc.getPageImageList(i):
xref = img[0]
pix = fitz.Pixmap(doc, xref)
pix1 = fitz.Pixmap(fitz.csRGB, pix)
page_count += 1
pix1.writePNG("images/picture_number_%s_from_page_%s.png" % (page_count, i+1))
print("Image number ", page_count, " writed...")
pix1 = None
В моем случае, код извлек из PDF файла 244 изображения. И все это произошло меньше чем за минуту! Результат:

Разделение PDF файлов на страницы с помощью PyPDF2
Для этого примера, в первую очередь необходимо импортировать классы PdfFileReader и PdfFileWriter. Затем мы открываем файл PDF, создаем объект для чтения и перебираем все страницы, используя метод объекта для чтения getNumPages. Внутри цикла for мы создаем новый экземпляр PdfFileWriter, который еще не содержит страниц. Затем мы добавляем текущую страницу к нашему объекту записи, используя метод pdfWriter.addPage(). Этот метод принимает объект страницы, который мы получаем, используя метод PdfFileReader.getPage(). Следующим шагом является создание уникального имени файла, что мы делаем, используя исходное имя файла плюс слово «page» плюс номер страницы. Мы добавляем 1 к текущему номеру страницы, потому что PyPDF2 считает номера страниц, начиная с нуля. Наконец, мы открываем новое имя файла в режиме (режиме wb) записи двоичного файла и используем метод write() класса pdfWriter для сохранения извлеченной страницы на диск. Все извлеченные страницы запишутся в папку dist. Сам код:

from PyPDF2 import PdfFileReader, PdfFileWriter
pdf_document = "source/ YourFile.pdf "
pdf = PdfFileReader(pdf_document)
for page in range(pdf.getNumPages()):
pdf_writer = PdfFileWriter()
current_page = pdf.getPage(page)
pdf_writer.addPage(current_page)
outputFilename = "dist/Computer-Vision-Resources-page-{}.pdf".format(page + 1)
with open(outputFilename, "wb") as out:
pdf_writer.write(out)
print("created", outputFilename)
Результат:

Найти все страницы, где есть заданный текст
Этот скрипт довольно практичен и работает аналогично pdfgrep. Используя PyMuPDF, скрипт возвращает все номера страниц, которые содержат заданную строку поиска. Страницы загружаются одна за другой и с помощью метода searchFor() обнаруживаются все вхождения строки поиска. В случае совпадения соответствующее сообщение печатается на stdout. В моем случае я нашел все страницы, которые содержат слово “Python”.

import fitz
filename = "source/ YourFile.pdf "
search_term = "COMPUTER VISION"
pdf_document = fitz.open(filename)
for current_page in range(len(pdf_document)):
page = pdf_document.loadPage(current_page)
if page.searchFor(search_term):
print("%s найдено на странице %i" % (search_term, current_page+1))
Результат:

Добавление водяного знака с помощью PyPDF2
В пакете PyPDF2 есть метод mergepage(), который использует другой PDF в качестве водяного знака или штампа. Сам PDF файл, который мы хотим, чтобы программа использовала в качестве водяного знака кладем в папку source (в моем случае это mshe-logo-512x512.pdf). Начнем с чтения первой страницы исходного PDF документа и водяного знака. Для чтения файла мы используем класс PdfFileReader(). На втором шаге эти две страницы объединяются с помощью метода mergepage(), после чего результат записывается в выходной файл. Эти три этапа — создание объекта на основе класса PdfFileWriter(), добавление объединенной страницы к этому объекту с помощью метода addPage() и запись нового контента на выходную страницу с помощью метода write(). При этом, получившийся новый PDF файл с водяным знаком появится в папке dist. Сам код:

# Добавление водяного знака в одностраничный PDF
import PyPDF2
input_file = "source/ YourFile.pdf "
output_file = "dist/ YourFile -page-drafted.pdf"
watermark_file = "source/mshe-logo-512x512.pdf"
with open(input_file, "rb") as filehandle_input:
# читать содержимое исходного файла
pdf = PyPDF2.PdfFileReader(filehandle_input)

with open(watermark_file, "rb") as filehandle_watermark:
# читать содержание водяного знака
watermark = PyPDF2.PdfFileReader(filehandle_watermark)

# получить первую страницу оригинального PDF
first_page = pdf.getPage(0)

# получить первую страницу водяного знака PDF
first_page_watermark = watermark.getPage(0)

# объединить две страницы
first_page.mergePage(first_page_watermark)

# создать объект записи PDF для выходного файла
pdf_writer = PyPDF2.PdfFileWriter()

# добавить страницу
pdf_writer.addPage(first_page)

with open(output_file, "wb") as filehandle_output:
# записать файл с водяными знаками в новый файл
pdf_writer.write(filehandle_output)
Результат:

Добавление изображения с помощью PyMuPDF
В данном коде, я добавил в мой PDF файл изображение, которое предварительно положил в папку source (в моем случае это image.jpg). Сам код:

import fitz
input_file = "source/ YourFile.pdf "
output_file = "dist/ YourFile -page-image.pdf"
barcode_file = "source/YourImage.jpg"
# определить позицию (верхний правый угол)
image_rectangle = fitz.Rect(450, 170, 550, 270)
# retrieve the first page of the PDF
file_handle = fitz.open(input_file)
first_page = file_handle[0]
# добавить изображение
first_page.insertImage(image_rectangle, filename=barcode_file)
file_handle.save(output_file)
Положение изображения определяется как rectangle (прямоугольник) методом fitz.Rect(), который требует двух пар координат — левый верхний угол (x1,y1) и правый нижний угол (x2,y2) изображения. В PyMuPDF левому верхнему углу страницы соответствуют координаты (0,0). После открытия входного файла и извлечения из него первой страницы с помощью метода insertImage() добавляется наше изображение. Этот метод требует двух параметров — позиционирование с использованием imageRectangle и имя файла изображения для вставки. С помощью метода save() измененный PDF файл сохраняется на диске. Результат:

Добавление штампов с pdfrw
pdfrw — это библиотека Python и утилита, которая читает и записывает PDF файлы. И перед тем как выполнять данное задание, эту библиотеку необходимо будет установить. После этого, мы из данного пакета импортируем три класса — PdfReader PdfWriter и PageMerge. Устанавливаем соответственно объекты чтения/записи, для доступа как к содержимому PDF, так и к нашему изображению. Для каждой страницы в исходном документе вы продолжаем создавать объекты PageMerge, к которому добавляем водяной знак и который отображается всё это с помощью метода render(). Сам код:

from pdfrw import PdfReader, PdfWriter, PageMerge
input_file = "source/ YourFile.pdf "
output_file = "dist/ YourFile-pages-image.pdf"
watermark_file = "source/mshe-logo-512x512.pdf "
# определяем объекты чтения и записи
reader_input = PdfReader(input_file)
writer_output = PdfWriter()
watermark_input = PdfReader(watermark_file)
watermark = watermark_input.pages[0]
# просматривать страницы одну за другой
for current_page in range(len(reader_input.pages)):
merger = PageMerge(reader_input.pages[current_page])
merger.add(watermark).render()
# записать измененный контент на диск
writer_output.write(output_file, reader_input)
Результат:

Удаление страниц с помощью PyMuPDF
Библиотека PyMuPDF включает в себя довольно много сложных методов, которые упрощают удаление страниц из файла PDF. Он позволяет вам указать либо одну страницу (метод deletePage()), либо диапазон номеров страниц (метод deletePageRange()), либо список с номерами страниц (метод select()). В следующем примере будет показано, как использовать список для выбора страниц, которые следует сохранить из исходного документа. Имейте в виду, что страницы, которые не указаны, не будут частью выходного документа. В нашем случае выходной документ содержит только первую, вторую и четвертую страницы:

import fitz
input_file = "source/YourFile.pdf "
output_file = "dist/YourFile -rearranged.pdf"
# Определите страницы для сохранения - 1, 2 и 4
file_handle = fitz.open(input_file)
pages_list = [0,1,3]
# Выберите страницы и сохраните вывод
file_handle.select(pages_list)
file_handle.save(output_file)
Результат:

Разделение четных и нечетных страниц с помощью PyPDF2
Следующий пример использует PyPDF2 и разделяет файл на четные и нечетные страницы, сохраняя четные страницы в файле Documentation-Python-even.pdf и нечетные страницы в Documentation-Python-odd.pdf. Этот скрипт Python начинается с определения двух выходных файлов, Documentation-Python-even.pdf и Documentation-Python-odd.pdf, а также соответствующие им объекты для записи pdf_writer_even и pdf_writer_odd. Затем в цикле for скрипт просматривает весь файл PDF и читает одну страницу за другой. Страницы с четными номерами страниц добавляются в поток pdf_writer_even с помощью addPage(), а нечетные номера добавляются в поток pdf_writer_odd. В конце два потока сохраняются на диск в отдельных файлах, как определено ранее. Сам код:

from PyPDF2 import PdfFileReader, PdfFileWriter
pdf_document = "source/ YourFile.pdf "
pdf = PdfFileReader(pdf_document)
# Выходные файлы для новых PDF-файлов
output_filename_even = "dist/ YourFile -even.pdf"
output_filename_odd = "dist/ YourFile -odd.pdf"
pdf_writer_even = PdfFileWriter()
pdf_writer_odd = PdfFileWriter()
# Получить досягаемую страницу и добавить ее в соответствующую
# выходной файл на основе номера страницы
for page in range(pdf.getNumPages()):
current_page = pdf.getPage(page)
if page % 2 == 0:
pdf_writer_odd.addPage(current_page)
else:
pdf_writer_even.addPage(current_page)
# Записать данные на диск
with open(output_filename_even, "wb") as out:
pdf_writer_even.write(out)
print("created", output_filename_even)
# Записать данные на диск
with open(output_filename_odd, "wb") as out:
pdf_writer_odd.write(out)
print("created", output_filename_odd)

Рубрики
Фреймворк

СКАНЕР ПОДДОМЕНОВ НА PYTHON

Поиск поддоменов — неотъемлемая часть подготовки ко взлому, а благодаря некоторым инструментам противостояние этим действиям стало намного проще.

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

В случае последнего из череды инцидентов весь код сайта Vine можно было загрузить с незащищенного поддомена.

Поэтому цель статьи - показать процесс получения списка поддоменов.

Откройте новый файл Python и следуйте инструкциям, давайте исследуем родной susu.ru в демонстрационных целях. Я использовал его, потому знаю, что на susu.ru много поддоменов:

import requests
# домен для поиска поддоменов
domain = "susu.ru"
Теперь нам понадобится большой список поддоменов для сканирования, я использовал список из 100 поддоменов только для демонстрации. У меня есть файл «subdomains.txt» в текущем каталоге:

# читать все поддомены
file = open("subdomains.txt")
# прочитать весь контент
content = file.read()
# разделить на новые строки
subdomains = content.splitlines()
Теперь список поддоменов содержит поддомены, которые мы хотим протестировать, давайте их переберем в цикле:

# список обнаруженных поддоменов
discovered_subdomains = []
for subdomain in subdomains:
# создать URL
url = f"http://{subdomain}.{domain}"
try:
# если возникает ОШИБКА, значит, субдомен не существует
requests.get(url)
except requests.ConnectionError:
# если поддомена не существует, просто передать, ничего не выводить
pass
else:
print("[+] Обнаружен поддомен:", url)
# добавляем обнаруженный поддомен в наш список
discovered_subdomains.append(url)
Сначала мы создаем URL-адрес, подходящий для отправки запроса, затем мы используем функцию requests.get() для получения HTTP-ответа от сервера, это вызовет исключение ConnectionError всякий раз, когда сервер не отвечает, поэтому мы обернули это в блоке try/except.

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

with open("discovered_subdomains.txt", "w") as f:
for subdomain in discovered_subdomains:
print(subdomain, file=f)
Результат исполнения кода:

Как только он будет завершен, вы увидите новый файл discover_subdomains.txt, который включает все обнаруженные поддомены!

Рубрики
Фреймворк

Работа с EMail

Как читать электронные письма в Python?

В ходе моего учебного процесса на паре было опробовано приложение, которое позволяло читать мои электронные письма, их удалять, писать и отправлять кому-либо от моего имени, как единичным письмом, так и рассылкой. Все это было реализовано с помощью встроенного модуля imaplib. Что вообще такое IMAP? IMAP — это стандартный протокол Интернета, который используется почтовыми клиентами для получения сообщений электронной почты с почтового сервера.
Начать я бы хотел с чтения отправленных мне писем. Для этого, нам не нужно ничего устанавливать, все модули, которые используются в этом руководстве, являются встроенными:

import imaplib
import email
from email.header import decode_header
import webbrowser
import os
# учетные данные
username = "youremailaddress @ provider.com"
password = "yourpassword"
def clean(text):
# чистый текст для создания папки
return "".join(c if c.isalnum() else "_" for c in text)

В данном блоке, мы импортировали необходимые модули, а затем указали учетные данные нашей электронной почты. Позже нам понадобится функция clean(), чтобы создавать папки без пробелов и специальных символов. Далее, мы подключаемся к серверу IMAP и так как я тестирую учетную запись gmail, то я использую сервер imap.gmail.com (для другой почты, необходим, соответственно, другой сервер):

# create an IMAP4 class with SSL
imap = imaplib.IMAP4_SSL("imap.gmail.com")
# authenticate
imap.login(username, password)

Далее, необходимо уже начинать получать электронные письма:

status, messages = imap.select("INBOX")
# количество популярных писем для получения
N = 3
# общее количество писем
messages = int(messages[0])

В данном блоке мы использовали метод imap.select(), который выбирает почтовый ящик (входящие, спам и т. Д.) и выбрали папку INBOX. Переменная messages содержит общее количество сообщений в этой папке (папке «Входящие»), а статус — это просто сообщение, которое указывает, успешно ли мы получили сообщение. Затем мы преобразовали сообщения в целое число, чтобы создать цикл for. Переменная N — это количество основных сообщений электронной почты, которые вы хотите получить, сейчас я буду использовать 3, давайте переберем каждое сообщение электронной почты, извлечем все, что нам нужно, и закончим наш код:

for i in range(messages, messages-N, -1):
# fetch the email message by ID
res, msg = imap.fetch(str(i), "(RFC822)")
for response in msg:
if isinstance(response, tuple):
# parse a bytes email into a message object
msg = email.message_from_bytes(response[1])
# decode the email subject
subject, encoding = decode_header(msg["Subject"])[0]
if isinstance(subject, bytes):
# if it's a bytes, decode to str
subject = subject.decode(encoding)
# decode email sender
From, encoding = decode_header(msg.get("From"))[0]
if isinstance(From, bytes):
From = From.decode(encoding)
print("Subject:", subject)
print("From:", From)
# if the email message is multipart
if msg.is_multipart():
# iterate over email parts
for part in msg.walk():
# extract content type of email
content_type = part.get_content_type()
content_disposition = str(part.get("Content-Disposition"))
try:
# get the email body
body = part.get_payload(decode=True).decode()
except:
pass
if content_type == "text/plain" and "attachment" not in content_disposition:
# print text/plain emails and skip attachments
print(body)
elif "attachment" in content_disposition:
# download attachment
filename = part.get_filename()
if filename:
folder_name = clean(subject)
if not os.path.isdir(folder_name):
# make a folder for this email (named after the subject)
os.mkdir(folder_name)
filepath = os.path.join(folder_name, filename)
# download attachment and save it
open(filepath, "wb").write(part.get_payload(decode=True))
else:
# extract content type of email
content_type = msg.get_content_type()
# get the email body
body = msg.get_payload(decode=True).decode()
if content_type == "text/plain":
# print only text email parts
print(body)
if content_type == "text/html":
# if it's HTML, create a new HTML file and open it in browser
folder_name = clean(subject)
if not os.path.isdir(folder_name):
# make a folder for this email (named after the subject)
os.mkdir(folder_name)
filename = "index.html"
filepath = os.path.join(folder_name, filename)
# write the file
open(filepath, "w").write(body)
# open in the default browser
webbrowser.open(filepath)
print("="*100)
# close the connection and logout
imap.close()
imap.logout()

Во-первых, range(messages, messages-N, -1), означает переход сверху вниз, то есть самые новые сообщения электронной почты имеют наивысший идентификационный номер, а первое сообщение электронной почты имеет идентификатор 1. Во-вторых, мы использовали метод imap.fetch(), который извлекает сообщение электронной почты по идентификатору, используя стандартный формат, указанный в RFC 822. После этого, мы анализируем байты, возвращаемые методом fetch(), на правильный объект Message и используем функцию decode_header() из email.header для декодирования темы электронного адреса в удобочитаемый юникод. После того, как мы напечатали отправителя электронной почты и тему, мы хотим извлечь тело сообщения. Мы проверяем, является ли электронное сообщение составным, а это означает, что оно состоит из нескольких частей. Например, сообщение электронной почты может содержать содержимое text/html и части text/plain, что означает, что у него есть HTML-версия и текстовая версия сообщения. Он также может содержать вложения файлов, мы обнаруживаем это по заголовку Content-Disposition, поэтому мы загружаем его в новую папку, созданную для каждого сообщения электронной почты, названного в честь темы. Объект msg, который является объектом сообщения модуля электронной почты, имеет много других полей для извлечения, в этом примере мы использовали только From и Subject, напишите msg.keys() и посмотрите доступные поля для извлечения, вы можете, например, получить дату отправки сообщения с помощью msg ["Date"]. Если же вам ничего не отправили, то он будет выводить «Писем нет».

Как удалить электронные письма в Python?

Чтобы автоматический удалять электронные письма в Python, мы все также используем встроенный модуль imaplib и начинаем с импорта необходимых модулей и указания учетных данных нашей учетной записи:

import imaplib
import email
from email.header import decode_header
# учетные данные
username = "youremailaddress @provider.com"
password = "yourpassword"

После этого, мы подключаемся к IMAP-серверу почтового провайдера:

# create an IMAP4 class with SSL
imap = imaplib.IMAP4_SSL("imap.gmail.com")
# authenticate
imap.login(username, password)

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

# select the mailbox I want to delete in
# if you want SPAM, use imap.select("SPAM") instead
imap.select("INBOX")

Итак, мы используем выбор почтового ящика с помощью метода imap.select(), после чего я выбрал папку INBOX. Теперь давайте выполним поиск писем, которые мы хотим удалить, по дате:

# для получения писем после определенной даты
status, messages = imap.search(None, 'SINCE "01-JAN-2020"')

status содержит строку, указывающую, был ли поиск успешно выполнен, messages возвращаются как список строки [[ASCII] почтовых идентификаторов, разделенных пробелом, преобразуем его в список целых чисел:

# преобразовать сообщения в список адресов электронной почты
messages = messages[0].split(b' ')
Теперь давайте переберем выбранные письма и отметим их как удаленные:
for mail in messages:
_, msg = imap.fetch(mail, "(RFC822)")
# вы можете удалить цикл for для повышения производительности, если у вас длинный список писем
# потому что он предназначен только для печати SUBJECT целевого электронного письма, которое нужно удалить
for response in msg:
if isinstance(response, tuple):
msg = email.message_from_bytes(response[1])
# расшифровать тему письма
subject = decode_header(msg["Subject"])[0][0]
if isinstance(subject, bytes):
# if it's a bytes type, decode to str
subject = subject.decode()
print("Deleting", subject)
# отметить письмо как удаленное
imap.store(mail, "+FLAGS", "\\Deleted")

Наконец, мы выполняем метод imap.expunge(), который навсегда удаляет сообщения, помеченные как удаленные, так же закрываем почтовый ящик и выходим из аккаунта:

# навсегда удалить письма, помеченные как удаленные
# из выбранного почтового ящика (в данном случае INBOX)
imap.expunge()
# закрыть почтовый ящик
imap.close()
# выйти из аккаунта
imap.logout()

Как отправлять электронные письма с Python?

В данном разделе мы будем использовать сразу несколько модулей, а именно: smtplib, email, предназначенный для отправки различных типов вложений, таких как HTML-контент и двоичные файлы, а также библиотека BeautifulSoup (которую нужно будет заранее установить), позволяющая автоматически извлекать простой текст из HTML, не беспокоясь о регулярных выражениях.
Ну и как обычно, начинаем мы с импорта всех библиотек:

import smtplib
from email import encoders
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from bs4 import BeautifulSoup as bs

Далее, мы определяем наши параметры:

# ваши учетные данные
email = "email @ example.com"
password = "password"
# электронная почта отправителя
FROM = "from @example.com"
# адрес электронной почты получателя
TO = "to @example.com"
# тема письма (тема)
subject = "Just a subject"

Переменные email и password — это учетные данные адреса электронной почты, который вы хотите отправить, FROM и TO — это адрес электронной почты отправителя и адрес получателя соответственно (адрес электронной почты и FROM обычно совпадают), а subject — это заголовок (или тема) письма, которое мы отправим.
Убедившись в том, что значения заданы правильно, вы можете отправить сообщения на несколько адресов электронной почты, просто используя их список переменной TO, а не единственный адрес.
Кроме того, мы будем использовать объект MIMEMultipart для одновременной передачи «альтернативы» в качестве подтипа, чтобы объединить две версии почты в одно сообщение с двумя вариантами отображения:

# инициализируем сообщение, которое хотим отправить
msg = MIMEMultipart("alternative")
# установить адрес электронной почты отправителя
msg["From"] = FROM
# установить адрес электронной почты получателя
msg["To"] = TO
# задаем тему
msg["Subject"] = subject

Также, устанавливаем адреса электронной почты From, To и subject. Создадим тело сообщения:

# установить тело письма как HTML
html = """
This email is sent using <b>Python</b>!
"""
# делаем текстовую версию HTML
text = bs(html, "html.parser").text

В этом примере мы установили html как простое сообщение HTML, но вы можете прочитать его из почтового HTML шаблона, например:

# установить тело письма как HTML
html = open("mail.html").read()
# делаем текстовую версию HTML
text = bs(html, "html.parser").text

Далее, мы завершаем построение сообщения и смотрим, как оно выглядит:

text_part = MIMEText(text, "plain")
html_part = MIMEText(html, "html")
# прикрепить тело письма к почтовому сообщению
# сначала прикрепите текстовую версию
msg.attach(text_part)
msg.attach(html_part)
print(msg.as_string())

Теперь, когда у нас есть письмо, готовое к отправке, нужно создать функцию, которая принимает адреса электронной почты FROM и TO, а также фактическое сообщение, которое нужно отправить, и она отправляет нам электронное письмо:

def send_mail(email, password, FROM, TO, msg):
# инициализировать SMTP-сервер
server = smtplib.SMTP("smtp.gmail.com", 587)
# подключиться к SMTP-серверу в режиме TLS (безопасный) и отправить EHLO
server.starttls()
# войти в учетную запись, используя учетные данные
server.login(email, password)
# отправить электронное письмо
server.sendmail(FROM, TO, msg.as_string())
# завершить сеанс SMTP
server.quit()

Итак, сначала мы подключаемся к SMTP-серверу с помощью smtplib, в этом примере мы использовали SMTP-сервер Gmail с портом 587. Затем мы переводим соединение с SMTP-сервером в режим TLS для безопасности, и, наконец, мы входим в систему, используя данные учётной записи и завершаем сеанс после отправки нашего электронного письма. Server.sendmail() выполняет всю почтовую транзакцию. У метода есть несколько аргументов: адрес, отправляющий письмо (FROM), список адресов получателей письма (если это строка, то она будет рассматриваться как список с 1 адресом) и сообщение для отправки. Вызовем только что созданную функцию:

# отправить почту
send_mail(email, password, FROM, TO, msg)

Как итог, проделав все вышесказанные действия, вы сможете с легкостью взаимодействовать со своим Email благодаря и используя Python.

Рубрики
Фреймворк

Конспект: Рынок рекрутмента сегодня и завтра

После первой волны локдауна, рынок труда начал достаточно серьезно оживать с середины мая, был очень серьёзный тренд на восстановление. На текущий момент, где-то в середине октября случилась вторая волна. Хоть и не было глобальных, жестких ограничений, было замедление темпов роста. Активизация прошла по всем отраслям кроме наиболее пострадавших. Коррекция очень сильная наблюдается в медицине, и госслужбе. Рынок вообще очень инертно отреагировал на текущий так называемый кризис.

Прежде всего, самое главное изменение это кризис 90-х годов, кризис 2000-х… Основное действие, которое топ-менеджеры, владельцы бизнеса, предпринимали, это сокращение персонала. Сокращали издержки, сокращение персонала. В этот же кризис компании в большинстве своем, старались сохранить персонал в штате любой ценой. Вовлечение человека, особенно нового в структуру, очень дорого. Есть множество примеров. Сократили персонал и так далее, но эти компании, скорее всего и останутся в самом неудобном, самом не выгодном положении. Потому что рекрутмент сейчас вещь безумно дорогая. Дорогая, потому что задач, бизнесов в России становится радикально больше, а вот талантливых людей, которые могут реализовывать эти задачи, создавать дополнительное value бизнесу, их остается такое же количество, и за них конкуренция. Чтобы найти этих людей, конкуренция усиливается, безусловно.

С самого начала локдауна наблюдается следующая диспозиция, которая, традиционно срабатывает во времена кризиса. Люди поделились на три неравные группы. Первая группа – это те, кого уволили, они уже ищут работу, вот они создают активность на рынке. Вторые – это те, кто подозревает, что их могут уволить, и пытаются подстелить соломку до того, как это произойдет. И основная группа, которая радикально больше чем первые две, это группа людей, которые замерли. Они замерли, они стараются не менять коней на переправе. Лучше синица в руке, чем журавль в небе, времена не спокойные, и это вполне нормальная логика поведения. Поэтому больших всплесков, то есть активности на рынке, на текущий момент не видно. Как только будет более понятная ситуация прежде всего с ковидом, как его лечить, как от него вакцинироваться, и что делать, то гораздо проще. Спадет психологический напряг. Давление, которое оказывается на людей. Они более спокойно выйдут на рынок.

Когда высокая конкуренция, ты не расслабляешься, ты стараешься генерить  какие-то вещи, которые могут быть полезны клиенту. И любой кризис, это новые возможности. Сейчас происходит новый технологический уклад. Многие познакомились с «цифрой» даже не предполагая, что с ней придется с ней знакомиться, особенно у нас в России. Поэтому конкуренция – это здорово.

На западе другая модель традиционно сложилась. Модель потребления данных сервисов. Основной единицей является вакансии, в отличие от России. Здесь традиционно резюме. Люди стараются искать. Потому что столько общаться с клиентами для того, чтобы сделать какие-то удобные фичи, это нужно отдельные подходы, и наверное, отдельные душевные качества иметь надо.

Абсолютно каждый бизнес ощущает внушительность затрат на найм одного сотрудника. В среднем это около 4000 рублей. Но в РФ сам по себе рекрутинг по стоимости очень низок, чем в Европе. Например, то, что у нас может стоит 100 долларов, там может стоить 1000. И найм всегда имеет тенденцию удорожания (особенно в условиях дефицита), потому что конкуренция уже идет на международном рынке из-за интернета, а не только на российском. И если компания хочет конкурировать, то надо инвестировать. Но можно и удешевить эту стоимость за счет ведения кадровых резервов, сотрудничества с университетами, открытия своих обучающих школ и так далее. Также, самыми востребованными навыки являются: самоорганизация, вливание в команду и самая важная - ответственность за свою работу.

Рубрики
Фреймворк

Использование ggplot в Python: визуализация данных с помощью plotnine

Содержание

В этом уроке вы узнаете, как использовать ggplot Python для создания визуализаций данных с использованием грамматики графики. Грамматика графики-это высокоуровневый инструмент, который позволяет создавать графики данных эффективным и последовательным способом. Он абстрагирует большинство низкоуровневых деталей, позволяя вам сосредоточиться на создании значимых и красивых визуализаций для ваших данных.

Существует несколько пакетов Python, которые предоставляют грамматику графики. Этот учебник фокусируется на пакете plotnine, так как он является одним из самых зрелых. Plotnine основан на ggplot2 языке программирования R, поэтому, если у вас есть фон в R, то вы можете рассматривать plotnine как эквивалент ggplot2 в Python.

В этом уроке вы узнаете, как это сделать.:

  • Установите Plotnine и Jupyter Notebook
  • Комбинируйте различные элементы грамматики графики
  • Используйте plotnine для создания визуализаций эффективным и последовательным способом
  • Экспорт визуализаций данных в файлы

Этот учебник предполагает, что у вас уже есть некоторый опыт работы с Python и, по крайней мере, некоторые знания о Jupyter Notebook и pandas. Чтобы быстро разобраться в этих темах, ознакомьтесь с Jupyter Notebook: An Introduction и используйте Pandas и Python для изучения Вашего набора данных.

Настройка Вашего Окружения

В этом разделе вы узнаете, как настроить свою среду. Вы будете освещать следующие темы:

  1. Создание виртуальной среды
  2. Установка плотнина
  3. Установка Ноутбука Juptyer

Виртуальные среды позволяют устанавливать пакеты в изолированных средах. Они очень полезны, когда вы хотите попробовать некоторые пакеты или проекты, не мешая вашей общесистемной установке. Вы можете узнать больше о них в Python Virtual Environments: A Primer.

Выполните следующие команды, чтобы создать каталог с именем data‑visualization и виртуальную среду внутри него:

$mkdir data-visualization
$cd data-visualization
$python3 -m venv venv

После выполнения вышеуказанных команд вы найдете свою виртуальную среду внутри data‑visualizationкаталога. Выполните следующую команду, чтобы активировать виртуальную среду и начать ее использовать:

$ source ./venv/bin/activate

Когда вы активируете виртуальную среду, любой устанавливаемый вами пакет будет установлен внутри среды, не влияя на вашу общесистемную установку.

Затем вы установите plotnine внутри виртуальной среды с помощью установщика pipпакетов.

Установите plotnine, выполнив эту команду:

$pip install plotnine

Выполнение приведенной выше команды делает plotnineпакет доступным в вашей виртуальной среде.

Наконец, вы установите Jupyter Notebook. Хотя это не совсем необходимо для использования plotnine, вы найдете Jupyter Notebook действительно полезным при работе с данными и построении визуализаций. Если вы никогда раньше не пользовались этой программой, то можете узнать о ней больше в Jupyter Notebook: An Introduction.

Чтобы установить Jupyter Notebook, используйте следующую команду:

$ pip install jupyter

Поздравляю, теперь у вас есть виртуальная среда с установленным ноутбуком plotnine и Jupyter! С помощью этой настройки вы сможете запустить все примеры кода, представленные в этом руководстве.

Построение вашего первого участка с ggplotпомощью and Python

В этом разделе вы узнаете, как построить свою первую визуализацию данных с помощью ggplotPython. Вы также узнаете, как проверять и использовать примеры наборов данных, включенных в plotnine.

Примеры наборов данных действительно удобны, когда вы знакомитесь с функциями plotnine. Каждый набор данных представлен в виде фрейма данных pandas, двумерной табличной структуры данных, предназначенной для хранения данных.

В этом учебнике вы будете работать со следующими наборами данных:

  • economics: Временной ряд экономических данных США
  • mpg: Данные по экономии топлива для ряда транспортных средств
  • huron: Уровень озера Гурон между 1875 и 1972 годами

Полный список примеров наборов данных можно найти в справочникеplotnine.

Вы можете использовать Jupyter Notebook для проверки любого набора данных. Запустите Jupyter Notebook с помощью следующих команд:

$ source ./venv/bin/activate
$ jupyter-notebook

Затем, оказавшись внутри Jupyter Notebook, выполните следующий код, чтобы просмотреть необработанные данные в economicsнаборе данных:

from plotnine.data import economics

economics

Код импортирует economicsнабор данных из plotnine.dataнего и отображает его в таблице:

      date      pce      pop   psavert uempmed  unemploy
0   1967-07-01  507.4   198712  12.5    4.5     2944
1   1967-08-01  510.5   198911  12.5    4.7     2945
... ...         ...     ...     ...     ...     ...
572 2015-03-01  12161.5 320707  5.2     12.2    8575
573 2015-04-01  12158.9 320887  5.6     11.7    8549

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

  • date: Месяц, когда были собраны данные.
  • pce: Расходы на личное потребление (в миллиардах долларов)
  • pop: Общая численность населения (в тысячах человек)
  • psavert: Норма личных сбережений
  • uempmed: Средняя продолжительность безработицы (в неделях)
  • unemploy: Число безработных (в тысячах человек)

Теперь, используя plotnine, вы можете создать график, чтобы показать эволюцию популяции на протяжении многих лет:

 1from plotnine.data import economics
 2from plotnine import ggplot, aes, geom_line
 3
 4(
 5    ggplot(economics)  # What data to use
 6    + aes(x="date", y="pop")  # What variable to use
 7    + geom_line()  # Geometric object to use for drawing
 8)

Этот короткий пример кода создает график из economicsнабора данных. Вот вам быстрая разбивка:

  1. Строка 1: вы импортируете economicsнабор данных.
  2. Строка 2: вы импортируете ggplot()класс, а также некоторые полезные функции из plotnineaes()и geom_line().
  3. Строка 5: вы создаете объект plot, используяggplot(), передавая economicsфрейм данных конструктору.
  4. Строка 6: вы добавляетеaes(), чтобы установить переменную для использования для каждой оси, в данном случае dateи pop.
  5. Строка 7: вы добавляетеgeom_line(), чтобы указать, что диаграмма должна быть нарисована в виде линейного графика.

Выполнение приведенного выше кода приводит к следующему результату:

Привет Плотнин пример

Вы только что создали сюжет, показывающий эволюцию популяции с течением времени!

В этом разделе вы увидели три обязательных компонента, которые необходимо указать при использовании грамматики графики:

  1. Данные, которые вы хотите построить
  2. Переменные, используемые на каждой оси
  3. Геометрический объект, используемый для рисования

Вы также видели, что различные компоненты объединяются с помощью +оператора.

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

Понимание грамматики графики

Грамматика графики-это высокоуровневый инструмент, который позволяет описывать компоненты графики, абстрагируя вас от низкоуровневых деталей собственно рисования пикселей на холсте.

Это называется грамматикой, потому что она определяет набор компонентов и правила их объединения для создания графики, так же как грамматика языка определяет, как вы можете комбинировать слова и знаки препинания, чтобы сформировать предложения. Вы можете узнать больше об основах грамматики графики в книге Лиланда Уилкинсона "грамматика графики".

Существует множество различных грамматик графики, и они отличаются по компонентам и правилам, которые они используют. Грамматика графики, реализованная plotnine, основана на ggplot2языке программирования R. Эта специфическая грамматика была представлена в статье Хэдли Уикхема " многослойная грамматика графики.”

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

  1. Данные - это информация, используемая при создании графика.
  2. Эстетика (aes) обеспечивает сопоставление между переменными данных и эстетическими, или графическими, переменными, используемыми базовой системой рисования. В предыдущем разделе вы сопоставили переменные dateи popданные с эстетическими переменными по осям x и Y.
  3. Geometric objects (geoms) определяет тип геометрического объекта, который будет использоваться на чертеже. Вы можете использовать точки, линии, бары и многие другие.

Без любого из этих трех компонентов плотнин не знал бы, как нарисовать график.

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

  • Статистические преобразования определяют вычисления и агрегации, применяемые к данным перед их построением.
  • Шкалы применяют некоторые преобразования во время отображения от данных к эстетике. Например, иногда вы можете использовать логарифмическую шкалу, чтобы лучше отразить некоторые аспекты ваших данных.
  • Фасеты позволяют разделить данные на группы на основе некоторых атрибутов, а затем построить каждую группу на отдельной панели на том же графике.
  • Системы координат отображают положение объектов в 2D графическом расположении на графике. Например, вы можете перевернуть вертикальную и горизонтальную оси, если это имеет больше смысла в создаваемой визуализации.
  • Темы позволяют управлять визуальными свойствами, такими как цвета, шрифты и формы.

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

Построение графиков данных с использованием Python и ggplot

В этом разделе вы узнаете больше о трех необходимых компонентах для создания визуализации данных с помощью plotnine:

  1. Данные
  2. Эстетика
  3. Геометрический объект

Вы также увидите, как они объединяются для создания графика из набора данных.

Данные: источник информации

Ваш первый шаг при создании визуализации данных-это указание того, какие данные нужно построить. В plotnine это делается путем создания ggplotобъекта и передачи конструктору набора данных, который вы хотите использовать.

Следующий код создает ggplotобъект, используя набор данных примера экономии топлива plotnine, mpg:

from plotnine.data import mpg
from plotnine import ggplot

ggplot(mpg)

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

Как вы уже видели ранее, вы можете проверить набор данных из записной книжки Jupyter с помощью следующего кода:

from plotnine.data import mpg

mpg

Эти две строки кода импортируют и показывают набор данных, отображая следующие выходные данные:

  manufacturer  model  displ  year  cyl  trans      drv  cty  hwy  fl  class
0 audi          a4      1.8   1999  4    auto(l5)   f    18   29   p  compact
1 audi          a4      1.8   1999  4    manual(m5) f    21   29   p  compact
2 audi          a4      2.0   2008  4    manual(m6) f    20   31   p  compact
...

Результатом является таблица, содержащая данные о расходе топлива для 234 автомобилей с 1999 по 2008 год. Поле Объем (displ) - это размер двигателя в литрах. cty и hwyэкономия топлива в милях на галлон для езды по городу и шоссе.

В следующих разделах вы узнаете, как превратить эти необработанные данные в графику с помощью plotnine.

Эстетика: определение переменных для каждой оси

После указания данных, которые вы хотите визуализировать, следующим шагом будет определение переменной, которую вы хотите использовать для каждой оси на графике. Каждая строка в фрейме данных может содержать много полей, поэтому вам нужно указать plotnine, какие переменные вы хотите использовать в графике.

Эстетика отображает переменные данных в графические атрибуты, такие как 2D-положение и цвет. Например, следующий код создает график, показывающий классы транспортных средств на оси x и расход топлива на шоссе на оси y:

from plotnine.data import mpg
from plotnine import ggplot, aes

ggplot(mpg) + aes(x="class", y="hwy")

Используя ggplotобъект из предыдущего раздела в качестве основы для визуализации, код отображает атрибут транспортного classсредства на горизонтальную графическую ось, а hwyэкономию топлива-на вертикальную ось.

Но сгенерированный график все еще пуст, потому что в нем отсутствует геометрический объект для представления каждого элемента данных.

Геометрические Объекты: Выберите Различные Типы Участков

После определения ваших данных и атрибутов, которые вы хотите использовать в графике, вам нужно указать геометрический объект, чтобы указать plotnine, как должны быть нарисованы точки данных.

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

Следующий код иллюстрирует, как использовать точечный геометрический объект для построения данных:

from plotnine.data import mpg
from plotnine import ggplot, aes, geom_point

ggplot(mpg) + aes(x="class", y="hwy") + geom_point()

В приведенном выше коде geom_point()выбирается точечный геометрический объект. Запуск кода приводит к следующему результату:

График, показывающий расход топлива для транспортных средств различных классов

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

Существует множество других геометрических объектов, которые можно использовать для визуализации одного и того же набора данных. Например, следующий код использует геометрический объект bar для отображения количества транспортных средств для каждого класса:

from plotnine.data import mpg
from plotnine import ggplot, aes, geom_bar

ggplot(mpg) + aes(x="class") + geom_bar()

Здесь geom_bar()геометрический объект устанавливается в положение bar. Поскольку код не указывает никакого атрибута для оси y, geom_bar()неявно группирует точки данных по атрибуту, используемому для оси x, а затем использует количество точек в каждой группе для оси Y.

Запустив код, вы увидите следующий вывод:

График количества транспортных средств в каждом классе с использованием баров

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

В этом разделе вы узнали о трех обязательных компонентах, которые необходимо указать при создании визуализаций данных:

  1. Данные
  2. Эстетика
  3. Геометрический объект

Вы также научились комбинировать их с помощью +оператора.

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

Использование дополнительных Python и ggplotфункций для улучшения визуализации данных

В этом разделе вы узнаете о дополнительных компонентах, которые можно использовать при построении визуализаций данных с помощью plotnine. Эти компоненты можно сгруппировать в пять категорий:

  1. Статистические преобразования
  2. Весы
  3. Система координат
  4. Грани
  5. Темы

Вы можете использовать их для создания более богатых и красивых сюжетов.

Статистические преобразования: агрегирование и преобразование ваших данных

Статистические преобразования применяют некоторые вычисления к данным перед их построением, например для отображения некоторого статистического показателя вместо исходных данных. plotnine включает в себя несколько статистических преобразований, которые вы можете использовать.

Допустим, вы хотите создать гистограмму для отображения распределения уровней озера Гурон с 1875 по 1975 год. Этот набор данных входит в состав plotnine. Вы можете использовать следующий код для проверки набора данных из Jupyter Notebook и узнать о его формате:

# Import our example dataset with the levels of Lake Huron 1875–1975
from plotnine.data import huron

huron

Код импортирует и показывает набор данных, производя следующие выходные данные:

    year  level   decade
0   1875  580.38  1870
1   1876  581.86  1870
...
96  1971  579.89  1970
97  1972  579.96  1970

Как вы можете видеть, набор данных содержит три столбца:

  1. year
  2. level
  3. decade

Теперь вы можете построить гистограмму в два этапа:

  1. Сгруппируйте измерения уровня в бункеры.
  2. Отображение количества измерений в каждом бункере с помощью гистограммы.

Следующий код показывает, как эти шаги могут быть выполнены в plotnine:

from plotnine.data import huron
from plotnine import ggplot, aes, stat_bin, geom_bar

ggplot(huron) + aes(x="level") + stat_bin(bins=10) + geom_bar()

В приведенном выше коде stat_bin()levelдиапазон делится на десять ячеек одинакового размера. Затем количество измерений, попадающих в каждую ячейку, рисуется с помощью гистограммы.

Запуск кода приводит к следующему графику:

Гистограмма уровня озера Гурон

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

Для большинства распространенных задач, таких как построение гистограмм, plotnine включает в себя очень удобные функции, которые делают код более сжатым. Например, с geom_histogram()помощью, вы можете построить приведенную выше гистограмму следующим образом:

from plotnine.data import huron
from plotnine import ggplot, aes, geom_histogram

ggplot(huron) + aes(x="level") + geom_histogram(bins=10)

Использованиеgeom_histogram()-это то же самое, что использование stats_bin()и тогда geom_bar(). Запуск этого кода генерирует ту же самую графику, которую вы видели выше.

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

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

  1. Сгруппируйте измерения по десятилетиям.
  2. Создайте прямоугольную диаграмму для каждой группы.

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

Затем, сгруппировав данные по десятилетиям ,вы можете нарисовать прямоугольную диаграмму для каждой группы с помощьюgeom_boxplot().

Следующий код создает график, используя шаги, описанные выше:

from plotnine.data import huron
from plotnine import ggplot, aes, geom_boxplot

(
  ggplot(huron)
  + aes(x="factor(decade)", y="level")
  + geom_boxplot()
)

Код группирует строки данных по десятилетиям с помощьюfactor(), а затем использует geom_boxplot()для создания коробчатых диаграмм.

Как вы видели в предыдущем примере, некоторые геометрические объекты имеют неявные статистические преобразования. Это очень удобно, так как делает ваш код более лаконичным. Использование geom_boxplot()подразумеваетstat_boxplot(), что заботится о вычислении квартилей и выбросов.

Запустив приведенный выше код, вы получите следующее графическое изображение:

Участок коробки уровня озера Гурон для каждого десятилетия

На графике показано распределение уровней воды с использованием коробчатого графика для каждого десятилетия.

Существуют и другие статистические преобразования, которые можно использовать для построения визуализаций данных с помощью ggplotPython. Вы можете узнать о них в документации plotnine's stats API.

Масштабирование: изменение масштаба данных в соответствии с его значением

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

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

from plotnine.data import economics
from plotnine import ggplot, aes, scale_x_timedelta, labs, geom_line

(
    ggplot(economics)
    + aes(x="date", y="pop")
    + scale_x_timedelta(name="Years since 1970")
    + labs(title="Population Evolution", y="Population")
    + geom_line()
)

Использование scale_x_timedelta()преобразует значение x каждой точки, вычисляя его разницу с самой старой датой в наборе данных. Обратите внимание, что код также используется labs()для установки более описательной метки для оси y и заголовка.

Запуск кода показывает этот график:

График, показывающий Дельта-шкалу даты, метки и заголовки

Не изменяя данные, вы сделали визуализацию более понятной и дружественной для читателя. Как вы можете видеть, график теперь имеет лучшие описания, а ось x показывает прошедшие годы с 1970 года вместо дат.

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

Системы координат: отображение значений данных в 2D-пространство

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

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

from plotnine.data import mpg
from plotnine import ggplot, aes, geom_bar

ggplot(mpg) + aes(x="class") + geom_bar()

Этот код используется geom_bar()для рисования полосы для каждого класса транспортных средств. Поскольку конкретная система координат не задана, используется система координат по умолчанию.

Запуск кода генерирует следующий график:

График количества транспортных средств в каждом классе с использованием баров

Высота каждой полосы на графике представляет собой количество транспортных средств в классе.

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

plotnine предоставляет несколько функций, которые позволяют изменять систему координат. Вы можете перевернуть оси с помощьюcoord_flip():

from plotnine.data import mpg
from plotnine import ggplot, aes, geom_bar, coord_flip

ggplot(mpg) + aes(x="class") + geom_bar() + coord_flip()

Код переворачивает оси x и y с помощьюcoord_flip(). Запустив код, вы увидите следующее графическое изображение:

Транспортные средства в каждом классе гистограмма с перевернутыми координатами

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

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

Фасеты: построение подмножеств данных на панелях в одном и том же графике

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

Например, предположим, что вы хотите взять набор данных об экономии топлива (mpg) и построить график, показывающий количество миль на галлон для каждого размера двигателя (displacement) для каждого класса транспортных средств за каждый год. В этом случае ваш график должен отображать информацию из четырех переменных:

  1. hwy: Мили на галлон
  2. displ: Размер двигателя
  3. class: Класс транспортного средства
  4. year: Модельный год

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

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

  1. Начните с разбиения данных на группы, где все точки данных в группе имеют одинаковые значения для некоторых атрибутов.
  2. Постройте график каждой группы по отдельности, показывая только атрибуты, не используемые в группировке.

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

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

Как вы можете видеть на приведенном выше рисунке, для каждой группы есть панель. Каждая панель показывает мили на галлон для различных перемещений двигателя, принадлежащих к этому классу транспортных средств и Году.

Эта визуализация данных была сгенерирована с помощью следующего кода:

from plotnine.data import mpg
from plotnine import ggplot, aes, facet_grid, labs, geom_point

(
    ggplot(mpg)
    + facet_grid(facets="year~class")
    + aes(x="displ", y="hwy")
    + labs(
        x="Engine Size",
        y="Miles per Gallon",
        title="Miles per Gallon for Each Year and Vehicle Class",
    )
    + geom_point()
)

The code partitions data by year and vehicle class using facet_grid(), passing it the attributes to use for the partitioning with facets="year~class". For each data partition, the plot is built using the components that you saw in previous sections, like aesthetics, geometric objects, and labs().

facet_grid() displays the partitions in a grid, using one attribute for rows and the other for columns. plotnine provides other faceting methods that you can use to partition your data using more than two attributes. You can learn more about them in plotnine’s facets API Reference.

Темы: улучшите внешний вид вашей визуализации

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

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

from plotnine.data import mpg
from plotnine import ggplot, aes, facet_grid, labs, geom_point, theme_dark

(
    ggplot(mpg)
    + facet_grid(facets="year~class")
    + aes(x="displ", y="hwy")
    + labs(
        x="Engine Size",
        y="Miles per Gallon",
        title="Miles per Gallon for Each Year and Vehicle Class",
    )
    + geom_point()
    + theme_dark()
)

В приведенном выше коде указание theme_dark()говорит плотнину нарисовать сюжет с использованием темной темы. Вот графическое изображение, сгенерированное этим кодом:

Темная тема плотнина

Как видно на рисунке, настройка темы влияет на цвета, шрифты и стили фигур.

theme_xkcd() это еще одна тема, которую стоит упомянуть, потому что она дает вам действительно классный комический вид. Это делает ваши визуализации данных похожими на комиксы xkcd:

Тема xkcd плотнина

Выбор правильной темы может помочь вам привлечь и удержать внимание вашей аудитории. Вы можете увидеть список доступных тем в справочнике plotnine themes API reference.

В предыдущих разделах вы узнали о наиболее важных аспектах грамматики графики и о том, как использовать plotnine для построения визуализаций данных. Использование ggplotв Python позволяет создавать визуализации постепенно, сначала фокусируясь на ваших данных, а затем добавляя и настраивая компоненты для улучшения их графического представления.

В следующем разделе вы узнаете, как использовать цвета и экспортировать свои визуализации.

Визуализация Многомерных Данных

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

Например, возвращаясь к набору данных топливной экономичности (mpg), предположим, что вы хотите визуализировать связь между количеством цилиндров двигателя и топливной экономичностью, но также хотите включить информацию о классах транспортных средств в тот же график.

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

Следующий код создает описанную визуализацию данных:

from plotnine.data import mpg
from plotnine import ggplot, aes, labs, geom_point

(
    ggplot(mpg)
    + aes(x="cyl", y="hwy", color="class")
    + labs(
        x="Engine Cylinders",
        y="Miles per Gallon",
        color="Vehicle Class",
        title="Miles per Gallon for Engine Cylinders and Vehicle Classes",
    )
    + geom_point()
)

Класс транспортного средства сопоставляется с графическим цветом, передавая color="class"эстетическое определение.

При запуске кода отображается эта графика:

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

Как вы можете видеть, точки имеют разные цвета в зависимости от класса, к которому принадлежит транспортное средство.

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

Экспорт графиков в файлы

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

plotnine предоставляет очень удобный save()метод, который вы можете использовать для экспорта графика в виде изображения и сохранения его в файл. Например, следующий фрагмент кода показывает, как можно сохранить графику, показанную в начале руководства, в файл с именемmyplot.png:

from plotnine.data import economics
from plotnine import ggplot, aes, geom_line

myPlot = ggplot(economics) + aes(x="date", y="pop") + geom_line()
myPlot.save("myplot.png", dpi=600)

В этом коде вы сохраняете объект визуализации данныхmyPlot, а затем вызываете save()его для экспорта графического изображения в виде изображения и сохранения его какmyplot.png.

Вы можете настроить некоторые настройки изображения при использованииsave(), например, изображение точек на дюйм (dpi). Это действительно полезно, когда вам нужны высококачественные изображения для включения в презентации или статьи.

plotnine также включает в себя метод сохранения различных сюжетов в одном PDF-файле. Вы можете узнать об этом и посмотреть несколько интересных примеров в документацииplotnine save_as_pdf_pages .

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

Вывод

Использование ggplotв Python позволяет создавать визуализации данных очень сжатым и последовательным способом. Как вы уже видели, даже сложные и красивые сюжеты могут быть сделаны с помощью нескольких строк кода с использованием plotnine.

В этом уроке вы узнали, как это сделать.:

  • Установите Plotnine и Jupyter Notebook
  • Комбинируйте различные элементы грамматики графики
  • Используйте plotnine для создания визуализаций эффективным и последовательным способом.
  • Экспорт визуализаций данных в файлы

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

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

Есть и другие пакеты визуализации данных Python, которые стоит упомянуть, такие как Altair и HoloViews. Взгляните на них, прежде чем выбрать инструмент для вашего следующего проекта. Затем используйте все, что вы узнали, чтобы построить некоторые удивительные визуализации данных, которые помогут вам и другим лучше понять данные!