Valiotti Analytics — построение аналитики для мобильных и digital-стартапов
    DataMarathon.ru — семидневный интенсив в области аналитики для начинающих
2 заметки с тегом

pandas

Экспорт исторических данных Apple Health в Google Sheets

Время чтения текста – 9 минут

Для устройств на базе iOS и watchOS существует приложение Health, которое ежедневно записывает все данные о здоровье носителя и синхронизирует их со сторонними приложениями. Все эти данные в любой момент можно получить прямо из приложения в виде XML-документа. Сегодня мы выгрузим исторические данные о здоровье из приложения Apple Health, обработаем их и отправим в Google Sheets для анализа и визуализации в будущем.

Экспорт архива из приложения

Зайдите в приложение Health на iPhone. Нажмите на аватарку своего профиля в верхнем правом углу — откроется меню приложения.

Внизу нажмите на кнопку «Экспортировать медданные». Через некоторое время откроется меню экспорта — отправьте архив себе на компьютер любым способом, можно по AirDrop или даже по почте в письме самому себе. Из архива нужен только один файл — «экспорт.xml». Достаньте его и положите в папку с ноутбуком jupyter.

Парсер XML в DataFrame

При помощи библиотеки XML составляем дерево на основе документа из Health. Собирать в словарь будем следующие атрибуты: тип, единица измерения, дата создания, дата начала, дата конца, значение. Проходим по всему дереву и отправляем полученные значения атрибутов в records_dict.

from xml.etree import ElementTree
import pandas as pd
import datetime

tree = ElementTree.parse('экспорт.xml')
root = tree.getroot()
records = root.findall('Record')

records_dict = {
    'type':[],
    'unit':[],
    'creationDate':[],
    'startDate':[],
    'endDate':[],
    'value':[]
}

for record in records:
    for attribute in records_dict.keys():
        attribute_value = record.get(attribute)
        records_dict[attribute].append(attribute_value)

События записаны в нечитабельном виде — для перевода составим специальный словарь с нужными типами, где ключ — старое название, а значение — новое. Мы возьмём только 11 событий: минуты осознанности, дистанция на велосипеде, дистанция заплыва, дистанция ходьбы и бега, пройдено пролётов, пульс, пульс в покое, шаги, активная энергия, энергия покоя и средний пульс при ходьбе.

types_dict = {
    'HKCategoryTypeIdentifierMindfulSession': 'Mindful Session',
    'HKQuantityTypeIdentifierDistanceCycling': 'Cycling Distance',
    'HKQuantityTypeIdentifierDistanceSwimming': 'Swimming Distance',
    'HKQuantityTypeIdentifierDistanceWalkingRunning': 'Walking + Running Distance',
    'HKQuantityTypeIdentifierFlightsClimbed': 'Flights Climbed',
    'HKQuantityTypeIdentifierHeartRate': 'Heart Rate',
    'HKQuantityTypeIdentifierRestingHeartRate': 'Resting Heart Rate',
    'HKQuantityTypeIdentifierStepCount': 'Steps',
    'HKQuantityTypeIdentifierActiveEnergyBurned': 'Active Calories',
    'HKQuantityTypeIdentifierBasalEnergyBurned': 'Resting Calories',
    'HKQuantityTypeIdentifierWalkingHeartRateAverage': 'Walking Heart Rate Average'
}

Для минут осознанности в поле значения записей нет — мы сами посчитаем позже это поле как разницу даты окончания и начала события. Разница будет представлена как timedelta, поэтому напишем функцию перевода timedelta в минуты:

def td_to_m(td):
    seconds = td.seconds + td.days * 24 * 60 * 60
    return seconds // 60

Из словаря создаём DataFrame и задаём названия колонок. Оставляем только те 11 событий, которые есть в словаре types_dict и приводим все колонки к нужным типам данных:

df = pd.DataFrame(records_dict)
df.columns = ['type', 'unit', 'date', 'start', 'end', 'value']
df = df[df['type'].isin(types_dict.keys())]
df['value'] = df['value'].astype(float)
df['date'] = df['date'].astype('datetime64')
df['date'] = df['date'].dt.date
df['start'] = df['start'].astype('datetime64')
df['end'] = df['end'].astype('datetime64')
df['unit'] = df['unit'].astype(str)

Данные Health при экспорте никак не группируются — мы сделаем это самостоятельно. DataFrame можно поделить на три: в первом будут события, у которых единица измерения «количество в минуту» — для таких событий нужно искать среднее значение. В другой группе будут минуты осознанности — считаем число минут в каждой записи и суммируем. В последней группе находятся все остальные записи, связанные с количественными событиями — шаги, дистанция ходьбы и бега и так далее. Их тоже суммируем.

df_1 = df[df['unit'] == 'count/min']
df_1 = df_1.groupby(by=['date', 'type', 'unit'], as_index=False).agg({'start':'min',
                                                                      'end':'max',
                                                                      'value':'mean'})

df_2 = df[df['type'] == 'HKCategoryTypeIdentifierMindfulSession']
df_2['value'] = df_2['end'] - df_2['start']
df_2['value'] = df_2['value'].map(td_to_m)
df_2 = df_2.groupby(by=['date', 'type', 'unit'], as_index=False).agg({'start':'min',
                                                                     'end':'max',
                                                                     'value':'sum'})
df_3 = df[(df['unit'] != 'count/min') & (df['type'] != 'HKCategoryTypeIdentifierMindfulSession')]
df_3 = df_3.groupby(by=['date', 'type', 'unit'], as_index=False).agg({'start':'min',
                                                                      'end':'max',
                                                                      'value':'sum'})
df = pd.concat([df_1, df_2, df_3])

Дату создания записи переводим в строковый тип. Все наименования типов событий заменяем согласно словарю types_dict. В переменную dates записываем все уникальные даты.

df['date'] = df['date'].astype(str)
df['type'] = df['type'].apply(lambda x: types_dict[x])
dates = df['date'].unique()

В результате нужен словарь с колонкой даты и отдельной колонкой под каждое из 11 событий:

result = {
    'date': [],
    'Steps': [],
    'Walking + Running Distance': [],
    'Swimming Distance': [],
    'Cycling Distance': [],
    'Resting Calories': [],
    'Active Calories': [],
    'Flights Climbed': [],
    'Heart Rate': [],
    'Resting Heart Rate': [],
    'Walking Heart Rate Average': [],
    'Mindful Session': []
}

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

for date in dates:
    part = df[df['date'] == date]
    result['date'].append(date)
    for key in result.keys():
        if key == 'date':
            continue
        else:
            field = 'value'
        try:
            result[key].append(part[part['type'] == key][field].values[0])
        except IndexError:
            result[key].append(None)

Из полученного словаря создаём DataFrame, округляем всё до двух знаков после запятой и сортируем по дате:

result_df = pd.DataFrame(result)
result_df = result_df.round(2)
result_df = result_df.sort_values(by='date')

В результате получается такая таблица с историческими данными по 11 событиям:

Экспорт DataFrame в Google Sheets

Для экспорта в Google Docs необходим сервисный аккаунт и json-файл с ключом. О том, как его получить, мы писали в материале «Собираем данные по рекламным кампаниям ВКонтакте»

Создайте новый документ в Google Sheets. Весь DataFrame можно вставить одним действием при помощи методов библиотеки gspread. Импортируйте её, а также укажите идентификатор документа и json-файл с ключом. В методе get_worksheet указывается порядковый номер листа в файле начиная с нуля.

import pandas as pd
import gspread
from gspread_dataframe import set_with_dataframe
gc = gspread.service_account(filename='serviceAccount.json')
sh = gc.open_by_key('1osKA63LQkUC0FC0eIZ63jEJwn1TeIkUvqCV6ur')
worksheet = sh.get_worksheet(0)

В итоге в Google Spreadsheets появится такая таблица:

А в следующем материале посмотрим, как наладить ежедневный экспорт данных Здоровья в эту таблицу при помощи шорткатов и Google AppScript!

Обзор библиотеки pandas-profiling на примере датасета Superstore Sales

Время чтения текста – 10 минут

Перед тем как работать с данными, необходимо составить представление, с чем мы имеем дело. В материале будем рассматривать датасет SuperStore Sales, а именно его лист Orders. В нём собраны данные о покупках клиентов канадского интернет-супермаркета: идентификаторы заказа, товаров, клиента, тип доставки, цены, категории и названия продуктов и прочее. Подробнее с датасетом можно ознакомиться на GitHub. Например, если мы создадим из датасета DataFrame, можем воспользоваться стандартным методом describe() библиотеки pandas для описания данных:

import pandas as pd

df = pd.read_csv('superstore_sales_orders.csv', decimal=',')
df.describe(include='all')

И во многих случаях получим такую кашу:

Код библиотеки доступен на GitHub

Если постараться и потратить время, можно извлечь полезную информацию. Например, можем узнать, что люди чаще выбирают «Regular air» в качестве доставки или что большинство заказов поступило из провинции Онтарио. Тем не менее, есть и другое решение, которое подробнее и качественнее описывает датасет — библиотека pandas-profiling. Вы отдаёте ей DataFrame, а она генерирует html-страницу с подробным описанием сета данных:

import pandas_profiling
profile = pandas_profiling.ProfileReport(df)
profile.to_file("output.html")

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

Web-версия отчёта доступна по ссылке

Обзор данных

Рассмотрим первый подраздел — «Overview». Библиотека собрала следующую статистику: количество переменных, наблюдений, пропущенных ячеек, дубликатов и общий вес файла. В колонке Variable types описаны типы переменных: здесь 12 качественных и 9 числовых.

В подразделе «Reproduction» собрана техническая информация библиотеки: сколько времени занял анализ сета данных, версия библиотеки и прочее.

А подраздел «Warnings» сообщает о возможных проблемах в структуре датасета: сейчас он, например, предупреждает, что у поля «Order Date» — слишком большое количество уникальных значений.

Переменные

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

При нажатии на Toggle details откроется расширенная информация: квартили, медиана и прочая полезная описательная статистика. В остальных вкладках находятся гистограмма из основного экрана, топ-10 значений по частоте и экстремальные значения.

Отношения переменных

В этом разделе визуализированы отношения переменных при помощи hexbin plot: выглядит это не очень очевидно и понятно. Особенно усугубляет положение отсутствие легенды к графику.

Корреляция переменных

В этом разделе представлена по-разному посчитананя корреляция переменных: например, первым указано r-value Пирсона. Заметно, что переменная Profit положительно коррелирует с переменной Sales. При нажатии на Toggle correlation descriptions открывается подробное пояснение к каждому коэффициенту.

Пропущенные значения

Тут всё просто — bar chart, матрица и дендрограмма с количеством заполненных полей в каждой переменной. Заметно, что в колонке Product Base Margin отсутствуют три значения.

Примеры

И, наконец, последний раздел представляет первые и последние 10 значений в качестве примера кусков сета данных — аналог метода head() из pandas.

Что в итоге?

Библиотека уделяет больше внимания статистике, чем pandas: можно получить подробную описательную статистику по каждой переменной, посмотреть, как коррелируют между собой столбцы датасета. В совокупности с генерацией простого и удобного интерфейса библиотека строит полноценный отчёт по датасету, уже на основании которого можно делать выводы и сформировать представление о данных.
И всё же, у библиотеки есть и минусы. На генерацию отчётов к громадным датасетам может уйти много времени вплоть до нескольких часов. Это безусловно хороший инструмент для автоматического проектирования, но он не может сделать полноценный анализ за вас и добавить больше деталей в графики. Кроме того, если вы только начали практиковаться с анализом данных лучше будет начать с pandas — это закрепит ваши навыки и придаст уверенности при работе с данными.