Новый ЕМИСС 2.0 со сводными таблицами, API и погодой?

от автора

Привет, Хабр! Около года назад мне пришла странная идея: а что, если сделать новую версию ЕМИСС, хранилища российской статистики, чтобы наконец-то было удобно сводить данные. А то постоянно сопоставлять несколько показателей из множества Excel файлов – сущий ад. И вот уже год прошел с момента создания и написания первой версии и сайта, и статьи (недавно был небольшой пост).

Что нового теперь в созданном приложении StatKit?

Обновление поиска: теперь поиск понимает и семантику за счет векторизации и сортировки по косинусной близости:

Поиск показателей

Поиск показателей

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

Фильтр по годам

Фильтр по годам
Фильтр по справочникам

Фильтр по справочникам
Фильтр по единицам измерения

Фильтр по единицам измерения

Свободные таблицы теперь позволяют объединить несколько показателей (я для примера выбрал население и число преступлений в целом и среди несовершеннолетних)

по субъектам РФ:

Кросс-секционный вид данных (все субъекты за один конкретный год)

Кросс-секционный вид данных (все субъекты за один конкретный год)

по одному субъекту в динамике:

Временной вид данных (за все годы по одному субъекту)

Временной вид данных (за все годы по одному субъекту)

все субъекты в динамике:

Панельный вид данных (все субъекты за все доступные годы)

Панельный вид данных (все субъекты за все доступные годы)

И для меня менее интересный вид, как в ЕМИСС, для одного показателя:

Один показатель в разрезе субъектов и по всем годам

Один показатель в разрезе субъектов и по всем годам

И, наверное, самое интересное теперь появился API для доступа к данным. 

Как начать работу с API StatKit?

1. Установка зависимостей

Откройте Jupyter Notebook. Убедитесь, что у вас установлены библиотеки requests и pandas

# установка и импорт зависимостей !pip install requests pandas  import pandas as pd import requests

2. Подготовка авторизации

Замените username и token на свои учетные данные API:

# получить имя пользователя и токен можно по адресу: https://go.statkit.ru/profile username = "your_username"  # замените на Ваш username API token = "your_token"        # замените на Ваш токен доступа

3. Настройка заголовков запроса

Добавьте headers для авторизации:

headers = {     "X-API-User": username,              # username API     "Authorization": f"Bearer {token}",  # токен     "Content-Type": "application/json"   # Формат данных (JSON) }

Получим список всех статистических показателей из API, преобразуем их в таблицу pandas:

api_url = "https://api.alexstat.ru/api/v1/indicators"  try:     # Выполняем GET-запрос к API     response = requests.get(api_url, headers=headers)      data = None          # Проверяем статус ответа сервера     if response.status_code == 200:         data = response.json()         print("Данные успешно получены!")     else:         print(f"Ошибка запроса. Код состояния: {response.status_code}")         print(f"Текст ответа сервера: {response.text}")  except requests.exceptions.RequestException as e:     print(f"Произошла ошибка при выполнении запроса: {e}")  # Преобразуем полученные данные в DataFrame df = pd.DataFrame(data) # Конвертируем строковые даты в формат datetime df['add_date'] = pd.to_datetime(df['add_date']) df['lastmod_date'] = pd.to_datetime(df['lastmod_date']) df
Результат запроса всех показателей

Результат запроса всех показателей

Можно сузить поиск, указав ключевые слова в запросе. Найдем показатели по ID или ключевому слову доля:

# URL API для получения данных о показателе "доля" api_url = "https://api.alexstat.ru/api/v1/indicators/доля"  try:     # Отправляем GET-запрос к API с указанными заголовками     response = requests.get(api_url, headers=headers)      # Инициализируем переменную для хранения данных     data = None          # Проверяем код статуса ответа     if response.status_code == 200:         # Если запрос успешен (код 200), преобразуем ответ в JSON         data = response.json()         print("Успешный запрос!")     else:         # Если сервер вернул ошибку, выводим код статуса и текст ответа         print(f"Ошибка запроса. Код статуса: {response.status_code}")         print(f"Ответ сервера: {response.text}")  # Обрабатываем возможные исключения при выполнении запроса except requests.exceptions.RequestException as e:     print(f"Ошибка при выполнении запроса: {e}")  # Преобразуем полученные данные в DataFrame pandas df = pd.DataFrame(data) df
Результат запроса всех показателей и указания ключевого слова

Результат запроса всех показателей и указания ключевого слова

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

# URL API для получения данных показателя с ID = 100 api_url = "https://api.alexstat.ru/api/v1/get_indicator/100"  try:     # Отправляем GET-запрос к API с указанными заголовками     response = requests.get(api_url, headers=headers)      # Инициализируем переменную для хранения данных     data = None          # Проверяем статус ответа сервера     if response.status_code == 200:         # При успешном ответе преобразуем в JSON         data = response.json()         print("Данные успешно получены!")     else:         # Выводим информацию об ошибке, если статус не 200         print(f"Ошибка запроса. Код статуса: {response.status_code}")         print(f"Текст ответа: {response.text}")  # Обрабатываем возможные исключения при выполнении запроса except requests.exceptions.RequestException as e:     print(f"Ошибка при выполнении запроса: {e}")  # Преобразуем данные в pandas DataFrame df = pd.DataFrame(data)  # Преобразуем значения показателя в числовой формат (float) df['ObsValue'] = df['ObsValue'].astype(float)  df
Результат запроса данных по конкретному показателю

Результат запроса данных по конкретному показателю

Полный код и готовый Jupyter Notebook в интерактивном Colab размещены по ссылке: https://go.statkit.ru/api

Также там в коде по ссылке я предлагаю вариант, как из нескольких эндпоинтов API провести мини-исследование и построить регрессию: исследовать зависимость между показателями населения и преступности по субъектам в разрезе ОКАТО:

Найдем интересующие нас показатели для исследования:

# URL API для получения показателей по ключевому слову "численность" api_url = "https://api.alexstat.ru/api/v1/indicators/численность"  try:     # Отправляем GET-запрос к API с указанными заголовками     response = requests.get(api_url, headers=headers)      # Инициализируем переменную для хранения данных ответа     data = None      # Проверяем статус ответа сервера     if response.status_code == 200:         # При успешном ответе преобразуем в JSON         data = response.json()         print("Данные успешно получены!")     else:         # Выводим информацию об ошибке, если статус не 200         print(f"Ошибка запроса. Код статуса: {response.status_code}")         print(f"Текст ответа сервера: {response.text}")  # Обрабатываем возможные исключения при выполнении запроса except requests.exceptions.RequestException as e:     print(f"Произошла ошибка при выполнении запроса: {e}")  # Преобразуем полученные данные в DataFrame df = pd.DataFrame(data) df
Показатели по ключевому слову "численность"

Показатели по ключевому слову «численность»

Численность постоянного населения имеет ID 1278.

# URL API для получения показателей по ключевому слову "преступ" api_url = "https://api.alexstat.ru/api/v1/indicators/преступ"  try:     # Отправляем GET-запрос с заголовками     response = requests.get(api_url, headers=headers)      # Переменная для хранения данных     data = None      # Проверяем статус ответа     if response.status_code == 200:         # Если успешно, преобразуем JSON         data = response.json()         print("Успешно!")     else:         # Выводим ошибку, если статус не 200         print(f"Ошибка запроса. Код: {response.status_code}")         print(f"Ответ: {response.text}")  # Обрабатываем ошибки запроса except requests.exceptions.RequestException as e:     print(f"Ошибка запроса: {e}")  # Создаем DataFrame из полученных данных df = pd.DataFrame(data) df
Показатели по ключевому слову "преступ"

Показатели по ключевому слову «преступ»

Далее, возьмем доля лиц, совершивших преступления, с ID = 232. Получим данные по показателю численность населения:

# URL для получения показателя с ID 1278 (численность постоянного населения) api_url = "https://api.alexstat.ru/api/v1/get_indicator/1278"  try:     # Отправка GET-запроса к API с указанными заголовками     response = requests.get(api_url, headers=headers)      # Инициализация переменной для хранения данных     data = None      # Проверка статуса ответа сервера     if response.status_code == 200:         # Успешный ответ - преобразуем в JSON         data = response.json()         print("Данные успешно получены!")     else:         # Вывод информации об ошибке, если статус не 200         print(f"Ошибка запроса. Код статуса: {response.status_code}")         print(f"Текст ответа: {response.text}")  # Обработка возможных ошибок при выполнении запроса except requests.exceptions.RequestException as e:     print(f"Ошибка выполнения запроса: {e}")  # Создание DataFrame из полученных данных population = pd.DataFrame(data)  # Преобразование значений показателя в числовой формат float population['ObsValue'] = population['ObsValue'].astype(float)  population
Данные показателя постоянного населения по субъектам РФ

Данные показателя постоянного населения по субъектам РФ

Обратимся к справочникам ОКАТО (1) и Тип поселения (17):

# Находим все колонки, содержащие справочники (начинающиеся с index_) print("Идентификация колонок со справочниками...") index_columns = [col for col in population.columns if col.startswith('index_')] print(f"Найдены колонки со справочниками: {index_columns}")  # Извлекаем ID справочников из названий колонок index_ids = [col.split('_')[1] for col in index_columns] print(f"Извлеченные ID справочников: {index_ids}")  # Функция для получения соответствия между кодами и их текстовыми описаниями def get_index_mapping(index_id):     """Получает маппинг кодов в текстовые описания для указанного справочника"""     api_url = f"https://api.alexstat.ru/api/v1/get_index/{index_id}"     try:         print(f"Запрашиваем маппинг для справочника {index_id}...")         response = requests.get(api_url, headers=headers)          if response.status_code == 200:             data = response.json()             # Создаем словарь {код: описание}             mapping = {str(item['code_value']): item['code_text'] for item in data}             print(f"Найдено {len(mapping)} соответствий для справочника {index_id}")             return mapping         else:             print(f"Ошибка получения справочника {index_id}: статус {response.status_code}")             return None     except requests.exceptions.RequestException as e:         print(f"Ошибка запроса для справочника {index_id}: {e}")         return None     except KeyError as e:         print(f"Неожиданный формат данных для справочника {index_id}: {e}")         return None  # Получаем маппинги для всех найденных справочников index_mappings = {} for index_id in index_ids:     mapping = get_index_mapping(index_id)     if mapping:         index_mappings[index_id] = mapping  print(f"Созданы маппинги для {len(index_mappings)} из {len(index_ids)} справочников")  # Заменяем коды на текстовые описания в основном DataFrame for col in index_columns:     index_id = col.split('_')[1]     if index_id in index_mappings:         mapping = index_mappings[index_id]         # Преобразуем к строковому типу, применяем маппинг, оставляем оригинальные значения при отсутствии маппинга         population[col] = population[col].astype(str).map(mapping).fillna(population[col])         print(f"Обработана колонка {col} (справочник {index_id})")     else:         print(f"Не найден маппинг для колонки {col} (справочник {index_id})")  # Выводим результат population
Добавили текстовые значения вместо кодов из справочников

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

Исследуем показатель, что он содержит:

# Проходим по всем колонкам в DataFrame population for col in population.columns:     # Пропускаем числовые колонки и идентификаторы     if col in ['ObsValue', 'id']:         continue      # Выводим название колонки     print(f'Уникальные значения в колонке {col}:')      # Выводим все уникальные значения в колонке     print(population[col].unique())      # Пустая строка для разделения     print()

Отфильтруем датафрейм со значениями показателя по населению. И возьмем статистику только с одного уровня – субъектного, исключив дублирования в виде РФ, федеральных округов, частей, входящих в состав субъектов:

OKATO_to_be_excluded = ['Российская Федерация',                         'Российская Федерация в границах до 04.10.2022',                         'Центральный федеральный округ',                         'Северо-Западный федеральный округ',                         'Ненецкий автономный округ (Архангельская область)',                         'Архангельская область (без АО)',                         'Архангельская область (кроме Ненецкого автономного округа)',                         'Южный федеральный округ (с 2010 года)',                         'Южный федеральный округ (с 29.07.2016)',                         'Северо-Кавказский федеральный округ',                         'Приволжский федеральный округ',                         'Коми-Пермяцкий округ, входящий в состав Пермского края',                         'Ханты-Мансийский автономный округ - Югра (Тюменская область)',                         'Ямало-Ненецкий автономный округ (Тюменская область)',                         'Тюменская область (без АО)',                         'Тюменская область (кроме Ханты-Мансийского автономного округа-Югры и Ямало-Ненецкого автономного округа)',                         'Сибирский федеральный округ',                         'Таймырский (Долгано-Ненецкий) автономный округ (Красноярский край)',                         'Эвенкийский автономный округ (Красноярский край)',                         'Усть-Ордынский Бурятский округ' ,                         'Агинский Бурятский округ (Забайкальский край)',                         'Корякский округ, входящий в состав Камчатского края',                         'Чеченская и Ингушская Республики',                         'Северный район' 'Северо-Западный район' 'Центральный район',                         'Волго-Вятский район',                         'Центрально-Черноземный район',                         'Поволжский район',                         'Северо-Кавказский район',                         'Уральский район',                         'Западно-Сибирский район',                         'Восточно-Сибирский район',                         'Дальневосточный район']  # Фильтрация данных о населении по нескольким условиям: population_filtered = population[     # 1. Выбираем только данные за 2024 год     (population['Time'] == 2024) &      # 2. Выбираем только записи, где указано "все население" (из справочника index_17)     (population['index_17'] == 'все население') &      # 3. Исключаем записи, где код региона (index_1) находится в списке исключений OKATO_to_be_excluded     # Оператор ~ означает отрицание (NOT IN)     (~population['index_1'].isin(OKATO_to_be_excluded)) ]  # Результат фильтрации сохраняется в новый DataFrame population_filtered population_filtered
Результат фильтрации показателя по населению

Результат фильтрации показателя по населению

Сделаем тоже самое для показателя по числу преступлений:

Делай раз:

api_url = "https://api.alexstat.ru/api/v1/get_indicator/232"  try:     response = requests.get(api_url, headers=headers)      data = None      if response.status_code == 200:         data = response.json()         print("Success!")     else:         print(f"Request failed with status code: {response.status_code}")         print(f"Response: {response.text}")  except requests.exceptions.RequestException as e:     print(f"Request failed: {e}")  crime = pd.DataFrame(data) crime['ObsValue'] = crime['ObsValue'].astype(float) crime

Делай два:

for col in crime.columns:     if col in ['ObsValue', 'id']:         continue     print(f'{col} unique values:')     print(crime[col].unique())     print()

Делай три:

for col in crime.columns:     if col in ['ObsValue', 'id']:         continue     print(f'{col} unique values:')     print(crime[col].unique())     print()

Делай четыре:

crime_filtered = crime[     (crime['Time'] == 2024) &     (crime['PERIOD'] == 'январь-декабрь') &     (crime['index_1'].isin(population_filtered['index_1'].tolist() ))] crime_filtered

И готово:

Результат фильтрации показателя по преступности

Результат фильтрации показателя по преступности

Объединим два показателя по ОКАТО (субъектам РФ):

# Объединение данных о населении и преступности в один DataFrame population_crime = pd.merge(     # Левый DataFrame - данные о населении:     population_filtered[['ObsValue', 'Time', 'index_1']].rename(columns={'ObsValue': 'population'}),      # Правый DataFrame - данные о преступности:     crime_filtered[['ObsValue', 'Time', 'index_1']].rename(columns={'ObsValue': 'crime'}),      # Параметры объединения:     on=['Time', 'index_1'],  # Ключи для объединения (год и код региона)     how='outer'              # Тип объединения (внешнее) )  # Результат объединения: population_crime
Объединили показатели "Преступность" и "Население"

Объединили показатели «Преступность» и «Население»

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

# раскомментируйте строчку ниже, чтобы установить библиотеку для рисования #!pip install matplotlib import matplotlib.pyplot as plt  plt.scatter(population_crime['population'], population_crime['crime']) plt.xlabel('Population') plt.ylabel('Crime') plt.show()
График разброса значений показателей населения и преступности

График разброса значений показателей населения и преступности

Регрессия:

# раскомментируйте строчку ниже, чтобы установить библиотеку для построения стат. моделей #!pip install statsmodels  import statsmodels.api as sm import matplotlib.pyplot as plt import numpy as np  X = population_crime['population']  # независимая переменная y = population_crime['crime']       # зависимая переменная  # добавим константу в уравнение X = sm.add_constant(X)  # регрессионная модель model = sm.OLS(y, X).fit()  # статистики модели print(model.summary()) print("\nCoefficients:") print(f"Intercept: {model.params[0]:.2f}") print(f"Population Coefficient: {model.params[1]:.4f}") print(f"R-squared: {model.rsquared:.3f}") print(f"P-value for Population: {model.pvalues[1]:.4f}")  # построим график plt.figure(figsize=(10, 6))  # график разброса наблюдаемых данных plt.scatter(     population_crime['population'],     population_crime['crime'],     color='blue',     alpha=0.6,     label='Фактические данные' )  # линия регрессии regression_line = model.params[0] + model.params[1] * population_crime['population'] plt.plot(     population_crime['population'],     regression_line,     color='red',     linewidth=2,     label=f'Regression Line\ny = {model.params[0]:.2f} + {model.params[1]:.4f}x' )  # уравнение регрессии equation_text = f'y = {model.params[0]:.2f} + {model.params[1]:.4f}x\nR² = {model.rsquared:.3f}' plt.text(     0.05, 0.95,     equation_text,     transform=plt.gca().transAxes,     fontsize=12,     verticalalignment='top',     bbox=dict(boxstyle='round', facecolor='white', alpha=0.8) )  plt.xlabel('Population', fontsize=12) plt.ylabel('Crime', fontsize=12) plt.title('Линейная регрессия', fontsize=14) plt.legend() plt.grid(True, alpha=0.3) plt.tight_layout() plt.show()
Коэффициенты и метрики регрессии

Коэффициенты и метрики регрессии

Модель показывает сильную зависимость уровня преступности от численности населения с R² = 0,829, что означает, что 82,9 % дисперсии преступности объясняется показателем населения. Коэффициент регрессии статистически значим: при росте населения на 1 000 человек число преступников увеличивается в среднем на 4 человека (как бы 4 человека из 1 000 значимо статистически могут стать преступниками). Однако, константа незначима (при «нулевом» населении). Такая вот псевдо-зависимость, но как пример использования API StatKit вполне себе подойдет.

И картинка:

Линейная регрессия

Линейная регрессия

И причем здесь погода скажите Вы? Хочу сообщить о запуске разработки нового формата – больших погодных массивов по Российской Федерации от ВНИИГМИ-МЦД в совместимом с ЕМИСС формате SDMX. Что скоро будет внедрено (по некоторым городам РФ данные доступны аж с конца 1800 годов!):

— суточные данные о температуре почвы по станциям в РФ;

— суточные данные о температуре атмосферного воздуха и осадках по станциям в РФ;

— суточные данные о снежном покрове по станциям в РФ.

Заключительные положения

Проект изначально был моим личным небольшим кодом, который помогал мне работать с ЕМИСС, но, как мне кажется, может он поможет и Вам.

Сейчас не все показатели есть у меня по сравнению с ЕМИСС, но я стараюсь добавлять с ЕМИСС новые данные, и, как я указывал в посте, если Вы напиши мне с указанием, какой показатель добавить (а еще лучше прямая ссылка на этот показатель в ЕМИСС), я его постараюсь оперативно добавить.

Еще раз положу здесь:

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

А Вы чем пользуетесь для доступа к статистике?

0% ЕМИСС0
0% Витрина данных Росстата0
0% Статистика с сайта Росстата0
0% Другое0

Никто еще не голосовал. Воздержавшихся нет.

ссылка на оригинал статьи https://habr.com/ru/articles/907124/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *