analytiX

Про веселую аналитику и практику программирования

Гистограмма по категориям

Все мы любим строить гистограммы и/или плотности распределения в своих любимых инструментах (matplotlib/seaborn/plotly/…. — выбрать чем пользуетесь). И все бы ничего, вызвал метод — получил результат, но тут столкнулся с интересным поведением.

Задача: Есть массив чисел (измерения некоего параметра), общее распределение представлено на рисунке 1 (сгенерировано с помощью python). В массиве выделяются категории величины и необходимо построить гистограмму по категориям.

import random

smp = pd.Series([random.gauss(100, 25) for _ in range(10000)])
smp.hist(bins=31)

Рис 1 Распределение некоего параметра

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

  • < 80
  • 80 — 125
  • > 125

Пишем функцию:

def categorize(value):
    if value < 80:
        return '< 80'
    elif value >= 80 and value <= 125:
        return '80 - 125'
    else:
        return '> 125'

Применяем к данным, на выходе категории:

smp_new['sample_category'].value_counts()

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

smp_new\
    .groupby('sample_category').apply(lambda gr: plt.hist(gr['sample'], bins=21, density=False))

Но результат:

Вопрос, как сделать так чтобы гистограмма по категориям была как исходная, но разноцветная🤔

Мысли вслух...
Что такое гистограмма — все значения величины ранжируются от меньшего к большему, весь диапазон делится на N отрезков (для функции plot это bins) и для каждого отрезка считается кол-во попавших в него значений исходной величины. Вот эти кол-ва и наносятся на график. Если мы получаем странную форму гистограммы, то логичным становится вопрос выбора N для графика по категориям.

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

UPD

Нашелся ответ тут

Спасибо, Петр Гонин

_, _, bars = plt.hist(smp_new['sample'], bins = 33, color="C0")
for bar in bars:
    if bar.get_x() < 80:
        bar.set_facecolor(color_triads[1][0])
    elif bar.get_x() <= 125 and bar.get_x() >= 80:
        bar.set_facecolor(color_triads[1][1])
    else:
        bar.set_facecolor(color_triads[1][2])
        
plt.ylabel("samples")
plt.grid(True)
plt.show()

Результат

 59   14 дн   #analytics   #histogramm   #python

Погодный монитор или Google SpreadSheets Dashboard

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

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

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

  • датчик освещенности
  • радиационный фон
  • скорость ветра
  • был и успел исчезнуть датчик пыли

Также станция обзавелась комнатным модулем (который связан по радиоканалу с уличным модулем и по последовательному интерфейсу с компьютером, где и происходит вся магия 🧙‍♂️) и в конце 2019 года появился дисплейный модуль

Цветовой индикатор отображает среднюю скорость роста давления за последние 3 часа (3 часа разбиваются на интервалы по 30 минут и считается среднее), имеет следующие цвета:

  • красный

    Рост свыше 1.8 мм/час — Quickly rising High Pressure System, not stable
  • желтый

    Рост выше 0.37 мм/час менее 1.8 мм/час — Slowly rising High Pressure System, stable good weather
  • зеленый

    Рост выше -0.37 мм/час и менее 0.37 мм/час — Stable weather condition
  • синий

    Рост более -0.37 мм/час — Slowly falling Low Pressure System, stable rainy weather
  • фиолетовый

    Рост более -1.8 мм/час — Quickly falling Low Pressure, Thunderstorm, not stable

За основу взят алгоритм компании NXP https://www.nxp.com/docs/en/application-note/AN3914.pdf.

После появления метеостанции естественным образом захотелось собирать данных и что еще более естественно их анализировать — это и стало мотиватором к изучению программирования. Были опробованы многие языки (processing (аля С++), php, bash, R). Но когда я открыл для себя python — почувствовал, вот оно!

Был написан скрипт, который запрашивает данные с комнатного модуля, принимает, проверяет и записывает в файл, таким образом начальная база файловая, она и осталась до сих пор. Иерархия такова Папка YYYYMM -> Файл YYYYMMDD.txt. Как говорится, цыплят по осени считают и пока учился в Яндекс.Практикуме, решил что надо весь погодный архив положить в базу данных и продолжить её наполнять и все это в облаке (модно/популярно/хайпово). Все прошло быстро, настройка виртуальной машины Ubuntu 18, установка PostgreSQL 10, создание схем/таблиц/пользователей/прав. В процессе решил не отказываться от хранения файлового архива, а параллельно записывать и в файл и в бд.

Все настроено — данные копятся (на данный момент, в погодной таблице 6.5 млн строк).

Я очень люблю всякие визуализации и с момента появления первых данных с метеостанции всегда хотел иметь приятную глазу картинку. Были разные варианты, но я не силен во фронтэнде, чтобы делать хоть какую-нибудь красоту🤷‍♂️. Не так давно попалась мне статья про дашборды с помощью google таблиц и тут я решил совместить приятное с полезным: SQL + python + Google Sheets.

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

  • SQL запрос, который формирует поминутные данные скользящих средних (окно около 3 минут) температуры, влажности, давления, скорости ветра и порывов ветра (максимальная скорость за 15 минут) и радиационный фон
  • python скрипт, который подключается к бд, выполняет запрос, осуществляет необходимую предобработку данных для google таблиц и апдейтит данные в нужном листе goolge таблиц
  • cron запускает python скрипт раз в 5 минут

Внешний вид дашборда (показатели и график температуры)

Там еще давление и скорость ветра с порывами есть, заходите

Weather Dashboard

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

 948   6 мес   #dashboard   #data   #google   #python   #weather

Data-view 2.0

...начало

Лето 2018. Анализ данных в R. Часть 1, Анализ данных в R. Часть 2
Осень 2018. Основы статистики. Часть 1, 2 и ... ну 3 не было, пока!

Кому-то эти курсы на stepik.org будут знакомы, кто-то проходит их сейчас, кто-то прошел давно, почти классика. В общем много было прочитано, просмотрено и изучено.
В апреле-мае 2019 курс «Введение в машинное обучение» окончательно дал понять, что тема da/ds/ml мне очень интересна. Процесс глобального поиска ресурсов по интересующим темам привел в Яндекс.Практикум https://praktikum.yandex.ru.

Тогда все только начиналось, оставим размышления на тему выбора, за/против и прочее, добавлю только одно — было круто. Знаете такой студенческий дух, в общем вспомнились интересные годы учебы в вузе)

...модуль

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

  • размерность
  • кол-во пропусков
  • дубликаты
  • описательные статистики
  • ...

В какой-то момент мне надоело писать это в ручную и была предпринята попытка написать парочку функций, чтобы автоматизировать этот процесс. Сие увлекательное занятие привело к написанию великолепной функции view. Из-за особенностей jupyter, в котором мы работали, ставить свои пакеты было не выгодно, они могли самоизолироваться при перезагрузке сервера, поэтому в проектах приходилось работать сочетанием клавиш Ctrl-C/Ctrl-V.
А для себя решил написать модуль и оформить его в виде пакета, чтобы можно было устанавливать на любой машине.

О, дивный новый мир git/github/pypi — освоили, запушили. И вот пакет data-view готов к употреблению!
Текущая версия 0.2.0 умеет:

  • совместный вывод head() + tail()
  • вывод метода info()
  • вывод метода describe()
  • кол-во дубликатов
  • кол-во пропусков в данных (кол-во пропущенных значений и процент от общего кол-ва), выделенных цветовым градиентом
  • гистограмма для каждого числового признака
  • топ-5 значений для категориальных признаков
  • матрица корреляций
  • топ-5 пар с наибольшей корреляцией

Сейчас модуль написан в функциональном стиле, но каждый раз его запуская, появляется потребность некоторые выделенные сущности иметь под рукой, собственно модуль напрашивается, быть переписанным в ООП стиле, и до этого доберусь!

Вся красота устанавливается привычным образом

pip3 install data-view

Красивый пример найдете здесь https://github.com/urevoleg/data-view

А я расскажу вам о естественных курьезах.

Если у вас многоколоночные данные и все данные числовые (например более 15 столбцов), то вот такую красоту вы увидите на гистограммах👇

Чертовски приятно, что работает, но сплошной винегрет)))

Порой можно получить разъезжающееся info()

ps: если кто-то знает как решить, буду благодарен)

Если уникальных элементов более 5, то группировки вы не увидите

...планы

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

  • pandas_profiling
  • autoEDA
  • holoviews
  • bamboolib (вообще шикарная штука😃)

Но какие-то показались мне громоздкими, где-то есть много лишнего, bamboolib, например очень интересная и прикольно-интерактивная, но вот рука как-то не тянется к ее использованию. Для себя хотелось чего-то простого, без наворотов, самого простого, реализовал и пользуюсь, большего пока и не надо...

Хотя, как не надо, переписать все классами и пользоваться всеми их преимуществами! Ну это в следующей серии!

 815   7 мес   #analytics   #data   #github   #python

Веселая аналитика. Lamptest💡💡

Работаем с данными и описательная статистика

Предыдущие части:
Веселая аналитика. Lamptest💡 (http://rweather.ru/blog/all/veselaya-analitika-lamptest-p1/?fbclid=IwAR3ULM3nfXwEvB9oOTlruAsm4qtoNMtGctmAxAit2nlbU8WlYs7cPxdwZhQ)

В первой части дилогии (трилогии 🤔) мы разобрались как автоматически собрать данные с сайтов, где контент подгружается динамически, спарсили данные и превратили их в pandas dataframe.

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

Напомню, что парсили сайт Алексея Надёжина — lamptest.ru LampTest в соцсетях:
Facebook: fb.com/lamptest.ru
Вконтакте: vk.com/lamptest
Geektimes: geektimes.ru/company/lamptest
Алексей тестирует светодиодные лампы, пишет обзоры и все это сведено в таблицу, по ней можно выбрать, что вас интересует и посмотреть всякие интересности!

Инструменты

  • python
  • matplotlib
  • pandas
  • plotly.express

Вся магия происходит в jupyter notebook

Данные

После открытия файла с данными

pd.read_csv(‘data/lamptest.csv’)

Пользуемся пакетом data_view https://pypi.org/project/data-view/ .

from data_view import view

Функция view покажет:
размер данных, кол-во пропущенных значений и дубликатов, отрисует гистограммы числовых признаков. И много других описательных статистик и не только.

Запускаем

view(bulbs, only_numeric=False)

Разбираем результат.
Признаки, имеющиеся в наличии:

  • brand — производитель лампы
  • model — наименование в модельном ряду
  • desc — описание (заявленные производителем параметры, в формате строки)
  • base — цоколь
  • shape — форма
  • price — цена
  • p — измеренная потребляемая мощность
  • lm_prc — отношение измеренного и заявленного светового потока
  • lm — измеренный световой поток
  • eff — эффективность (кол-во люмен на ватт)
  • eq — эквивалент мощности лампы накаливания
  • color — измеренная цветовая температура
  • cri — измеренный индекс цветопередачи
  • angle — измеренный угол освещения
  • flicker — измеренный коэффициент пульсации
  • rating — рейтинг по шкале от 1 до 5

Кол-во пропусков в данных

Категориальные признаки

Топ-5 элементов категориального признака

Промежуточный итог

  • дубликатов нет
  • признаки с наибольшим кол-вом пропусков:
  • цена (price)
  • угол освещения (angle)
    Наибольшее кол-во брендов:
    • Gauss — 182
    • X-Flash — 144
    • Navigator — 136
    • Наносвет — 132
    • Эра — 128
      Кол-во представленных цоколей:
    • Е27 — 1321 — стандартный цоколь
    • Е14 — 922 — “маленький” цоколь, миньон
      Кол-во ламп разной формы:
    • Bulb — 894 (лампа “классической” формы)
    • Spot — 516 (точечный светильник)
    • Candle — 462 (лампа-свеча)

Предобработка

в данных есть признак desc — заявленные производителем параметры (параметры, представленные в виде строки, параметры разделены пробелом). Попробуем извлечь из них ценность.

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

bulbs['desc'].map(lambda x: len(x.split())).value_counts()

Результат на рисунке ниже:

  • 4 элемента — 2376
  • 3 элемента — 311
  • 5 элементов — 137
  • 6 элементов — 7
  • 2 элемента — 4
  • 1 элемент — 2

Записи с длиной 4 и 3 составляют 94% всех данных на них и остановимся. Расположение необходимых элементов в строке указано на изображении ниже.

Создаем в таблице новые признаки:

  • color_init — заявленную цветовую температуру
  • lm_init — заявленный световой поток
  • p_init — заявленная мощность

Аналогичным образом поступаем с записями из трех частей. Дополним таблицу относительным изменением заявленных параметров:

bulbs['color_delta'] = (bulbs['color'] - bulbs['color_init']) / bulbs['color_init']
bulbs['lm_delta'] = (bulbs['lm'] - bulbs['lm_init']) / bulbs['lm_init']
bulbs['p_delta'] = (bulbs['p'] - bulbs['p_init']) / bulbs['p_init']

Немного статистики

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

Таблица форм и наименований цоколей электрических ламп

Самые популярные формы колб:
Bulb — грушевидная

Candle — свеча

Spot — точечный светильник

G45 — шарообразная

Посмотрим на распределение цен на лампы у каждого бренда. Сделан срез данных (лампочки мощностью от 7-12 Ватт и бренды, у которых более 50 моделей ламп)

Для тех, кто хочет интерактивности 👉 https://urevoleg.github.io/plotly-graph/bulbs_price_by_brand.html
Самая дорогая лампочка у Gauss — 1479 руб, самая дешевая Эра — 58 руб.
Бренды REV, Camelion, Diall, Эра — имеют малый разброс цены, самые «ходовые» модели ламп у них в одном ценовом сегменте 100 — 200 руб, у всех остальных брендов сильный разброс на цены, можно найти как дешевые, дорогие лампы, так и лампы средней цены.

На сайте есть собственный рейтинг ламп (рейтинг числовой 0 — 5 баллов), разметим его, с помощью таких правил:

  • > 4.5 — отлично
  • <= 4.5 and > 4 — хорошо
  • <= 4 and > 3.5 — удовлетворительно
  • <= 3.5 плохо
def get_str_rating(r):
    if not np.isnan(r):
        if r > 4.5:
            return 'отлично'
        elif r > 4 and r <= 4.5:
            return 'хорошо'
        elif r > 3.5 and r <= 4:
            return 'удовлетворительно'
        else:
            return 'плохо'
    else:
        return np.nan

Более 50% ламп имеют хороший рейтинг от Алексея Надёжина + авторские правила, 27% — плохой рейтинг и отличных ламп чуть более 3%.

Посмотрим на бренды «отличных» ламп:
Магия pandas 🧙‍♂️

bulbs.query("brand in @brands_list and p_init > 6 and p_init < 13 and rating_str=='отлично'").groupby('brand')['model'].count()

IKEA и Наносвет — лидеры по кол-ву ламп с отличным рейтингом!

Распределение цен по «качественному рейтингу», интересно, что можно нарваться на высокую (даже на очень) цену при удовлетворительном качестве.

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

  • составим свой рейтинг на основе имеющихся данных 📊
  • посмотрим, значимо ли различаются цены при разных уровнях рейтинга 🤔
  • рассмотрим на сколько и в какую сторону чаще всего отклоняются заявленные характеристики
  • какой из брендов отвечает за свои слова
  • попробуем завести machine learning (попробуем предсказать цену на модели, у которых она пропущена) 😈

Веселая аналитика. Lamptest💡

Продолжаем серию веселых аналитических статей.
Предыдущие части:
Веселая аналитика. Радиостанции (http://rweather.ru/blog/all/veselaya-analitika-radiostancii/)

Если вам близка тема светодиодного освещения и вы хотите знать какие лампы выбрать то вам идти на сайт хорошего человека Алексея Надёжина (LampTest в соцсетях:
Facebook: http://fb.com/lamptest.ru
Вконтакте: http://vk.com/lamptest
Geektimes: http://geektimes.ru/company/lamptest).

Алексей тестирует лампы, пишет обзоры и все это сведено в таблицу, по ней можно выбрать, что вас интересует и посмотреть всякие интересности!
А пока мы тут сгенерим Алексею несколько визитов на сайт.
Стоит сказать спасибо однокурснику Саше Михайлову (ссылка на его блог), у которого я подглядел идею блога и таких вот статей, спасибо тебе дорогой товарищ, что стал мотивацией!
В статьях продолжаем применять и расширять навыки, приобретенные на специализации “Аналитик данных” Яндекс.Практикум. Среди прочих интересных тем, была тема про извлечение данных с помощью вебскрепинга (ссылка на вики).

Инструментарий

  • python
  • requests
  • bs4 (BeautifullSoup) — библиотека для вебскрепинга
  • selenium — используем для получения содержимого динамических веб-страниц
  • tqdm — красивости для циклов
  • pandas
  • data_view — библиотека для первичного обзора данных

Решив, что все будет быстро и безболезненно, открыл jupyter notebook, импортировал нужные библиотеки (pandas, requests, bs4). При открытии главной страницы сайта нам предоставлен выбор фильтров и только одна галочка установлена (поле “Тип лампы” = LED), нам собственно так и нужно — получить всю информацию. Нажимаем на кнопку “Показать” и у нас таблица, есть страницы, их аж 59, надо разобраться как ходить по страничкам!?.
Посмотрим на url полученной страницы — http://lamptest.ru/search/#currency=rub&type=LED — пока ничто в нем не говорит о наличии номера страницы. Но если перейти на вторую, то он изменится — http://lamptest.ru/search/#currency=rub&type=LED&start=50, для 3 страницы url выглядит так  — http://lamptest.ru/search/#currency=rub&type=LED&start=100, делаем вывод, что для первой страницы необходимо установить параметр start = 0. Так и поступим.

Получается такой pipeline:

  • формируем правильный адрес страницы (с помощью цикла и range)
  • получаем содержимое веб-страницы (requests.get)
  • передаем код страницы в парсер BeautifullSoup
  • ищем нужные теги и классы
  • складываем информацию в датафрейм

Чтобы узнать какие элементы html нам нужны открываем консоль разработчика в браузере (Яндекс.Браузер, F12). Выбираем пиктограмму с курсором в верхнем левом углу консоли (она должна стать синей) — в таком режиме вы выбираете элемент и исследуете его.

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

В моем случае это “brand”. И если поизучать столбцы таблицы, заметим, что все они находятся в элементах с id=td и разными классами (brand, model, desc и др.). Эти параметры надо передать методу find_all супа, приготовленного из кода страницы. На выходе список всех найденных элементов, у каждого необходимо изъять текст.

res = soup.find_all("td", class_=cl)
i.text for i in res

Пишем код, запускаем, на выходе пусто🧐. Проверяем все еще раз, смотрим другие теги — все одно пусто! А давайте узнаем, что мы получаем в коде страницы при помощи

r = requests.get(url)

Вызываем атрибут и смотрим.

r.text

А в коде страницы нет нужной нам информации. Google в помощь. Гуглим и выясняется, что бывает парсинг статических страниц и парсинг динамических страниц.
Статический парсинг, парсинг страниц, на которых все элементы присутствуют сразу, без подгрузки во время запроса. Для такого парсинга подойдет способ, которым я пытался решить задачу.
Динамические страницы, те на которых используются скрипты JS, Ajax и другие, они в реальном времени подгружают информацию. Для парсинга таких страниц необходима эмуляция поведения браузера. Чтобы этого достичь используется библиотека selenium и ее компонент webdriver. Драйвер позволяет автоматически управлять браузером.
Для того чтобы связка selenium+webdriver+браузер работала необходима установка компонентов. В документации этот процесс описан достаточно подробно https://selenium.dev/documentation/en/webdriver/driver_requirements/.
Важное замечание: вначале я пытался пойти простым путем, как гласила документация в версиях Safari старше 10, webdriver идет в комплекте, классно подумал я, надо только запустить. Запускаться webdriver отказался по непонятным причинам, после команды

safaridriver -p 1337

в консоле тишина.
Из оставшихся браузеров (Opera, Firefox, Chrome) выбор пал на Google Chrome. По ссылке в документации скачал chromedriver, скопировал в нужную папку, установил браузер и понадеявшись на все последние версии, запустил chromedriver и чудо произошло, драйвер запустился! Драйвер запускает локальный сервер к которому и происходит обращение через selenium.

Но selenium.webdriver отказался подключался к драйверу, сославшись на разницу в версиях🤦‍♂️. Проверяем версии, chrome 78.xxxxx, chromedriver 79. Что же идем качаем совместимую с браузером версию chromedriver (качаем, копируем).
Теперь, когда все версии правильные, selenium подключается к chromedriver. И мы готовы получать данные.

Измененный pipeline:

  • формируем правильный адрес страницы (с помощью цикла и range)
  • открываем веб-страницу (driver.get)
  • получаем ее содержимое (driver.page_source)
  • передаем содержимое страницы в парсер BeautifullSoup
  • ищем нужные теги и классы
  • складываем информацию в датафрейм

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

Поставить теги #yandex #yandexpraktikum #praktikum
Ранее Ctrl + ↓