Найти в Дзене

Объектно-ориентированный подход к предобработке геопространственных данных: разработка системы автоматизированного анализа GPS-треков

Автоматизация предобработки геопространственных данных является актуальной задачей в области анализа мобильности и пространственной аналитики. Настоящая работа представляет объектно-ориентированную архитектуру для комплексной обработки GPS-треков, реализованную в виде класса GPXAgent. Система обеспечивает полный цикл обработки данных: от парсинга GPX-файлов до визуализации, статистического анализа и оптимизации признакового пространства. Модульная структура класса позволяет гибко комбинировать методы предобработки в зависимости от требований конкретной задачи. Современные системы анализа геопространственных данных требуют автоматизации рутинных операций предобработки для обеспечения масштабируемости и воспроизводимости результатов. Процедурный подход к обработке GPS-треков характеризуется высокой связанностью кода и затрудняет повторное использование логики. Объектно-ориентированное программирование предоставляет инструменты для инкапсуляции сложной логики обработки данных в переисполь
Оглавление

Аннотация

Автоматизация предобработки геопространственных данных является актуальной задачей в области анализа мобильности и пространственной аналитики. Настоящая работа представляет объектно-ориентированную архитектуру для комплексной обработки GPS-треков, реализованную в виде класса GPXAgent. Система обеспечивает полный цикл обработки данных: от парсинга GPX-файлов до визуализации, статистического анализа и оптимизации признакового пространства. Модульная структура класса позволяет гибко комбинировать методы предобработки в зависимости от требований конкретной задачи.

Введение

Современные системы анализа геопространственных данных требуют автоматизации рутинных операций предобработки для обеспечения масштабируемости и воспроизводимости результатов. Процедурный подход к обработке GPS-треков характеризуется высокой связанностью кода и затрудняет повторное использование логики. Объектно-ориентированное программирование предоставляет инструменты для инкапсуляции сложной логики обработки данных в переиспользуемые компоненты.

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

Материалы и методы

Архитектура системы

Система реализована в виде класса GPXAgent, инкапсулирующего состояние (данные) и поведение (методы обработки). Основные компоненты архитектуры включают:

  1. Конструктор (init): инициализация и первичная загрузка данных
  2. Методы обогащения: добавление производных признаков (температура, регион, окружение, шаги)
  3. Методы визуализации: создание карт и графиков распределений
  4. Методы анализа: статистические тесты и кластеризация
  5. Методы оптимизации: отбор информативных признаков

Такая структура обеспечивает четкое разделение ответственности и упрощает сопровождение кода.

Импорт библиотек

Технологический стек включает стандартные и специализированные библиотеки для работы с геопространственными данными.

# Стандартные библиотеки Python для математических операций и работы с файловой системой
import math
import os
import random

# Библиотека для выполнения HTTP-запросов к внешним API
import requests

# Специализированная библиотека для парсинга GPX-файлов (GPS Exchange Format)
import gpxpy

# Фундаментальные инструменты для работы с табличными данными и численными массивами
import pandas as pd
import numpy as np

# Геопространственные библиотеки для геокодирования и расчета расстояний
from geopy.geocoders import Nominatim
from geopy.distance import geodesic

# Библиотека для работы с данными OpenStreetMap и анализа окружающей среды
import osmnx as ox

# Инструменты для веб-автоматизации и создания интерактивных карт
import selenium
import folium
import io

# Обработка и аугментация изображений
from PIL import Image, ImageEnhance

# Библиотеки для визуализации данных и статистической графики
import seaborn as sb
import matplotlib.pyplot as plt

# Статистический анализ и проверка гипотез
from scipy import stats

# Алгоритмы машинного обучения: деревья решений, кластеризация, снижение размерности
from sklearn import tree
from sklearn.cluster import KMeans
from sklearn.decomposition import PCA

Представленный стек обеспечивает полную функциональность для всех этапов конвейера обработки геопространственных данных.

Реализация класса GPXAgent

Конструктор и инициализация данных

Конструктор класса выполняет загрузку исходных данных и первичный парсинг GPS-треков.

class GPXAgent:
"""
Класс для автоматизированной обработки и анализа GPS-треков.

Attributes:
gpx_table (pd.DataFrame): Основная таблица с данными GPS-точек и признаками
map_paths (pd.Series): Пути к сохраненным картам треков
inf_table: Дополнительная информационная таблица
df (pd.DataFrame): Исходные данные из CSV-файла
"""

def __init__(self, gpx_data_path, max_track):
"""
Инициализация агента и загрузка данных.

Parameters:
gpx_data_path (str): Путь к CSV-файлу с GPX-треками
max_track (int): Максимальное количество треков для обработки
"""
# Инициализация основной структуры данных с явно определенной схемой
self.gpx_table = pd.DataFrame(columns = [
"track_id", # Уникальный идентификатор GPS-трека
"point_id", # Глобальный порядковый номер точки
"time", # Временная метка точки (datetime)
"latitude", # Географическая широта (градусы)
"longitude", # Географическая долгота (градусы)
"elevation" # Высота над уровнем моря (метры)
])

# Инициализация вспомогательных атрибутов
self.map_paths = None # Будет заполнено при создании карт
self.inf_table = None # Резерв для дополнительной информации

# Загрузка исходных данных с обработкой исключений
try:
self.df = pd.read_csv(gpx_data_path)
except Exception as e:
print(f"Ошибка загрузки данных: {e}")
return

# Инициализация счетчиков для уникальных идентификаторов
track_id = 1 # Счетчик треков
point_id = 1 # Глобальный счетчик точек (сквозная нумерация)

# Итерация по всем GPX-трекам в исходном датасете
for i in self.df["gpx"]:
# Парсинг XML-строки GPX в объектную модель
# gpxpy.parse() преобразует текст в древовидную структуру данных
gpx = gpxpy.parse(i)

# Первый уровень иерархии: треки (tracks)
for track in gpx.tracks:
# Второй уровень: сегменты (segments)
# Трек может содержать несколько сегментов (например, при паузах)
for segment in track.segments:
# Третий уровень: точки (points)
# Сегмент содержит последовательность GPS-точек
for point in segment.points:
# Создание строки DataFrame для текущей точки
line = pd.DataFrame({
"track_id": [track_id], # ID текущего трека
"point_id": [point_id], # Глобальный ID точки
"time": [point.time], # Временная метка
"latitude": [point.latitude], # Широта
"longitude": [point.longitude], # Долгота
"elevation": [point.elevation] # Высота
})

# Копирование метаданных трека из исходного датасета
cope_date = pd.DataFrame(self.df.iloc[track_id-1]).T

# Добавление новой строки к основному датафрейму
# ignore_index=True обеспечивает автоматическую переиндексацию
self.gpx_table = pd.concat(
[self.gpx_table, line],
axis=0,
ignore_index=True
)

# Инкремент счетчика точек
point_id += 1

# Инкремент счетчика треков после обработки всех сегментов
track_id += 1

# Ограничение обработки заданным количеством треков
# Позволяет контролировать объем обрабатываемых данных
if track_id == max_track + 1:
break

Конструктор реализует паттерн "fail-fast" с обработкой исключений при загрузке данных, что обеспечивает устойчивость к ошибкам ввода-вывода. Параметр max_track позволяет ограничить объем обрабатываемых данных для тестирования или работы с подвыборками.

Метод обогащения метеорологическими данными

Метод pars_temperature интегрирует данные о температуре воздуха для каждой GPS-точки.

def pars_temperature(self):
"""
Добавление температурных данных для каждой точки маршрута.

Выполняет HTTP-запросы к погодному API wttr.in для получения
актуальной температуры по географическим координатам.

Returns:
pd.DataFrame: Обновленная таблица с колонкой 'temperature'
"""
# Инициализация списка для накопления температурных значений
total_column = []

# Получение списка индексов для безопасной итерации
indices = self.gpx_table.index.tolist()

# Итерация по всем точкам маршрута
for i in range(len(indices)):
# Извлечение текущей точки
row = self.gpx_table.loc[i]

# Выполнение HTTP-запроса с обработкой сетевых ошибок
try:
# Формирование URL с координатами и форматом ответа JSON
response = requests.get(
f"https://wttr.in/{row['latitude']},{row['longitude']}?format=j1"
)
except Exception as e:
print(f"Ошибка обращения к API: {e}")
continue

# Парсинг JSON-ответа от сервера
answer = response.json()

# Извлечение текущей температуры в градусах Цельсия
# current_condition[^0] содержит актуальные погодные условия
total_column.append(answer['current_condition'][^0]['temp_C'])

# Преобразование списка в pandas Series
total_column = pd.Series(total_column)

# Именование столбца для ясности
total_column.name = "temperature"

# Добавление нового столбца к основному датафрейму
self.gpx_table = pd.concat([self.gpx_table, total_column], axis=1)

# Возврат обновленной таблицы для возможности цепочки вызовов
return self.gpx_table

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

Метод геокодирования

Метод pars_region выполняет обратное геокодирование координат для определения страны.

def pars_region(self):
"""
Определение страны для каждой GPS-точки через обратное геокодирование.

Использует Nominatim (OpenStreetMap) для преобразования координат
в структурированные адреса с извлечением названия страны.

Returns:
pd.DataFrame: Обновленная таблица с колонкой 'country'
"""
# Инициализация списка для хранения названий стран
country_column = []

# Создание объекта геокодера с пользовательским агентом
# user_agent обязателен для идентификации приложения в запросах к API
geolocator = Nominatim(user_agent="_")

# Получение списка индексов
indices = self.gpx_table.index.tolist()

# Итерация по всем точкам маршрута
for i in range(len(indices)):
# Извлечение текущей точки
row = self.gpx_table.loc[i]

# Выполнение обратного геокодирования
# Формат входа: "широта, долгота" (строка)
location = geolocator.reverse(
f"{row['latitude']}, {row['longitude']}"
)

# Извлечение названия страны с обработкой отсутствующих данных
try:
# location.raw содержит полный словарь адресных компонентов
# ['address']['country'] извлекает название страны
country_column.append(location.raw['address']["country"])
except KeyError as e:
# Если страна не определена (например, точка в океане)
# добавляется None для сохранения согласованности размерности
country_column.append(None)

# Преобразование списка в pandas Series
country_column = pd.Series(country_column)

# Именование столбца
country_column.name = "country"

# Добавление столбца к основному датафрейму
self.gpx_table = pd.concat([self.gpx_table, country_column], axis=1)

# Возврат обновленной таблицы
return self.gpx_table

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

Метод анализа окружающей среды

Метод pars_features извлекает информацию о типе местности через OpenStreetMap.

def __check_features(self, lat, lon, radius=500):
"""
Приватный метод для анализа окружения GPS-точки.

Запрашивает данные OpenStreetMap о водных объектах, лесах и строениях
в заданном радиусе от точки.

Parameters:
lat (float): Широта точки
lon (float): Долгота точки
radius (int): Радиус поиска в метрах (по умолчанию 500м)

Returns:
dict: Словарь с количеством объектов каждого типа
{'water': int, 'forest': int, 'buildings': int}
"""
# Формирование кортежа координат для osmnx
point = (lat, lon)

# Определение тегов OSM для каждой категории объектов
# Теги соответствуют стандарту OpenStreetMap
tags = {
"water": {"natural": "water"}, # Водные объекты
"forest": {"natural": "wood"}, # Лесные массивы
"buildings": {"building": True} # Любые строения
}

# Инициализация словаря результатов
features = {}

# Итерация по категориям объектов
for key, tag in tags.items():
try:
# Запрос геометрий из OSM в заданном радиусе от точки
# features_from_point возвращает GeoDataFrame с объектами
gdf = ox.features.features_from_point(point, dist=radius, tags=tag)

# Подсчет количества найденных объектов
features[key] = len(gdf)
except Exception as e:
# При отсутствии данных или ошибке запроса присваивается 0
features[key] = 0

# Возврат словаря с количественными показателями
return features


def pars_features(self):
"""
Добавление признаков окружающей среды для GPS-точек.

Для каждой n-ой точки анализирует окружение и классифицирует тип местности.
Промежуточные точки получают дублированные значения (предположение о
пространственной автокорреляции).

Returns:
pd.DataFrame: Обновленная таблица с колонками water_feature,
forest_feature, buildings_feature, place_type
"""
# Инициализация списков для различных типов объектов
water_column = [] # Количество водных объектов
forest_column = [] # Количество лесных массивов
buildings_column = [] # Количество строений
type_place_column = [] # Категориальный признак типа местности

# Параметр интервала обработки
# Анализ выполняется только для каждой n-ой точки для оптимизации
n = 3

# Получение списка индексов
indices = self.gpx_table.index.tolist()

# Итерация с шагом n для снижения количества API-запросов
for i in range(0, len(indices), n):
# Извлечение текущей точки
row = self.gpx_table.loc[i]

# Вызов приватного метода анализа окружения
features = self.__check_features(
lat=row["latitude"],
lon=row["longitude"]
)

# Дублирование полученных значений для n последовательных точек
# Обосновано малыми расстояниями между соседними GPS-точками
for _ in range(n):
# Добавление количественных признаков
water_column.append(features["water"])
forest_column.append(features["forest"])
buildings_column.append(features["buildings"])

# Классификация типа местности на основе преобладающих объектов
# Логика основана на сравнении длин накопленных списков
if len(water_column) > len(forest_column) > len(buildings_column):
type_place_column.append("water")
elif len(forest_column) > len(water_column) > len(buildings_column):
type_place_column.append("forest")
else:
type_place_column.append("buildings")

# Преобразование списков в pandas Series
water_column = pd.Series(water_column)
forest_column = pd.Series(forest_column)
buildings_column = pd.Series(buildings_column)
type_place_column = pd.Series(type_place_column)

# Именование столбцов
water_column.name = "water_feature"
forest_column.name = "forest_feature"
buildings_column.name = "buildings_feature"
type_place_column.name = "place_type"

# Последовательное добавление всех признаков к датафрейму
self.gpx_table = pd.concat([self.gpx_table, water_column], axis=1)
self.gpx_table = pd.concat([self.gpx_table, forest_column], axis=1)
self.gpx_table = pd.concat([self.gpx_table, buildings_column], axis=1)
self.gpx_table = pd.concat([self.gpx_table, type_place_column], axis=1)

# Удаление строк с пропущенными значениями
# Необходимо из-за возможных несоответствий длин при интервальной обработке
self.gpx_table = self.gpx_table.dropna()

# Возврат обновленной таблицы
return self.gpx_table

Использование приватного метода __check_features инкапсулирует логику запросов к OSM и улучшает тестируемость кода. Параметр radius позволяет настраивать масштаб анализа окружения.

Метод расчета физической активности

Метод pars_steps вычисляет количество шагов между последовательными точками.

def pars_steps(self):
"""
Вычисление количества шагов между последовательными GPS-точками.

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

Returns:
pd.DataFrame: Обновленная таблица с колонкой 'steps'
"""
# Инициализация списка для хранения значений
total_column = []

# Константа средней длины шага человека (метры)
# Эмпирическое значение, используемое в фитнес-трекерах
step_distance = 0.7

# Получение списка индексов для безопасной итерации
indices = self.gpx_table.index.tolist()

# Итерация по всем парам последовательных точек
for i in range(len(indices) - 1):
# Извлечение текущей и следующей точек
row = self.gpx_table.loc[i]
next_row = self.gpx_table.loc[i + 1]

# Вычисление геодезического расстояния между точками
# geodesic использует формулу Винсенти для эллипсоида WGS-84
# Входные данные: кортежи (широта, долгота)
distance = geodesic(
(row["latitude"], row["longitude"]),
(next_row["latitude"], next_row["longitude"])
)

# Преобразование расстояния в количество шагов
# distance.meters возвращает расстояние в метрах
# Деление на step_distance дает оценку шагов
# round() округляет до целого числа
total_column.append(round(distance.meters / step_distance))

# Добавление нулевого значения для последней точки
# У последней точки нет "следующей" точки для расчета расстояния
total_column.append(0)

# Преобразование списка в pandas Series
total_column = pd.Series(total_column)

# Именование столбца
total_column.name = "steps"

# Добавление столбца к датафрейму
self.gpx_table = pd.concat([self.gpx_table, total_column], axis=1)

# Возврат обновленной таблицы
return self.gpx_table

Метод создает физически интерпретируемый признак, полезный для задач оценки физической активности и калорий.

Метод создания интерактивных карт

Метод create_maps генерирует визуализации треков на картах.

def create_maps(self, save_path):
"""
Создание и сохранение интерактивных карт для каждого GPS-трека.

Для каждого трека создается карта folium с наложенной полилинией маршрута,
которая затем рендерится в PNG-изображение.

Parameters:
save_path (str): Путь к директории для сохранения изображений карт

Returns:
pd.Series: Series с путями к сохраненным файлам изображений
"""
# Инициализация Series для хранения путей к картам
self.map_paths = pd.Series("paths", name="map_paths")

# Список для накопления точек текущего трека
points = []

# Счетчик обработанных карт
count = 0

# Вычисление центроидов (средних координат) для каждого трека
# Используется для центрирования карты
avg_lat = self.gpx_table.groupby("track_id")["latitude"].mean()
avg_lon = self.gpx_table.groupby("track_id")["longitude"].mean()

# Объединение в единый датафрейм
avg_coordinates = pd.concat([avg_lat, avg_lon], axis=1)
avg_coordinates.columns = ["avg_latitude", "avg_longitude"]

# Получение индексов обоих датафреймов
indices_avg_coordinates = avg_coordinates.index.tolist()
indices_gpx_table = self.gpx_table.index.tolist()

# Итерация по всем трекам
for i in range(len(indices_avg_coordinates)):
# Извлечение центроида текущего трека
row = avg_coordinates.loc[i+1]

# Создание объекта интерактивной карты folium
# location - координаты центра карты [широта, долгота]
# zoom_start - начальный уровень масштабирования (14 - городской масштаб)
m = folium.Map(
location=[row["avg_latitude"], row["avg_longitude"]],
zoom_start=14
)

# Сбор всех точек, принадлежащих текущему треку
for k in range(len(indices_gpx_table)):
row_gpx = self.gpx_table.loc[k]

# Проверка принадлежности точки к текущему треку
if row_gpx["track_id"] == i+1:
# Добавление координат в список точек маршрута
points.append((row_gpx["latitude"], row_gpx["longitude"]))

# Добавление полилинии (ломаной линии) на карту
# points - список кортежей координат
# tooltip - всплывающая подсказка при наведении мыши
# weight - толщина линии в пикселях
folium.PolyLine(points, tooltip="Coast", weight=5).add_to(m)

# Рендеринг HTML-карты в растровое изображение PNG
# _to_png(5) - внутренний метод folium с задержкой 5 секунд
# Задержка необходима для полной загрузки тайлов карты
img_data = m._to_png(5)

# Открытие изображения из байтового потока в памяти
# io.BytesIO создает файлоподобный объект из байтов
img = Image.open(io.BytesIO(img_data))

# Сохранение изображения на диск
# Путь формируется из параметра save_path и номера карты
img.save(f'{save_path}\\map_{count}.png')

# Сохранение пути к файлу в Series
self.map_paths.loc[count] = [f'{save_path}\\map_{count}.png']

# Инкремент счетчика карт
count += 1

# Возврат Series с путями к сохраненным картам
return self.map_paths

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

Метод аугментации изображений

Метод augmentation_image создает вариации карт для расширения датасета.

def augmentation_image(self):
"""
Аугментация изображений карт для расширения обучающего датасета.

Применяет случайные трансформации яркости, контраста и резкости
к каждому исходному изображению карты, создавая 6 вариаций.
Используется для увеличения объема данных при обучении моделей
компьютерного зрения.
"""
# Проверка наличия созданных карт
if type(self.map_paths) == None:
print("Невозможно сделать аугментацию изображений, так как они не было созданы. "
"Чтобы их создать, вспользуйтесь функций create_map")
return

# Получение индексов Series с путями к картам
indices = self.map_paths.index.tolist()

# Итерация по всем изображениям карт
for i in range(len(indices)):
# Извлечение пути к текущему изображению
row = self.map_paths[i]

# Загрузка изображения с диска
img = Image.open(row[^0])

# Создание объектов для трансформации изображения
brightness_changer = ImageEnhance.Brightness(img) # Яркость
contrast_changer = ImageEnhance.Contrast(img) # Контраст
sharpness_changer = ImageEnhance.Sharpness(img) # Резкость

# === Аугментация 1: Уменьшение яркости ===
# Коэффициент < 1.0 затемняет изображение
modified_image = brightness_changer.enhance(random.uniform(0.5, 0.9))
try:
# Сохранение с суффиксом _modified_1
modified_image.save(f"{row[^0][:-4]}_modified_{1}.png")
except Exception as e:
print(f"Ошибка в сохранении изображения: {e}")

# === Аугментация 2: Увеличение яркости ===
# Коэффициент > 1.0 осветляет изображение
modified_image = brightness_changer.enhance(random.uniform(1.01, 1.3))
try:
modified_image.save(f"{row[^0][:-4]}_modified_{2}.png")
except Exception as e:
print(f"Ошибка в сохранении изображения: {e}")

# === Аугментация 3: Уменьшение контраста ===
# Снижает различие между светлыми и темными областями
modified_image = contrast_changer.enhance(random.uniform(0.5, 0.9))
try:
modified_image.save(f"{row[^0][:-4]}_modified_{3}.png")
except Exception as e:
print(f"Ошибка в сохранении изображения: {e}")

# === Аугментация 4: Увеличение контраста ===
# Усиливает различие между светлыми и темными областями
modified_image = contrast_changer.enhance(random.uniform(1.1, 1.6))
try:
modified_image.save(f"{row[^0][:-4]}_modified_{4}.png")
except Exception as e:
print(f"Ошибка в сохранении изображения: {e}")

# === Аугментация 5: Уменьшение резкости (размытие) ===
# Создает эффект небольшого размытия
modified_image = sharpness_changer.enhance(random.uniform(0.5, 0.9))
try:
modified_image.save(f"{row[^0][:-4]}_modified_{5}.png")
except Exception as e:
print(f"Ошибка в сохранении изображения: {e}")

# === Аугментация 6: Увеличение резкости ===
# Подчеркивает границы и детали
modified_image = sharpness_changer.enhance(random.uniform(1.1, 1.6))
try:
modified_image.save(f"{row[^0][:-4]}_modified_{6}.png")
except Exception as e:
print(f"Ошибка в сохранении изображения: {e}")

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

Метод визуализации распределений

Метод graphic_distribution создает графики для разведочного анализа данных.

def graphic_distribution(self):
"""
Визуализация распределений всех признаков датасета.

Для числовых признаков строит гистограммы с наложенной оценкой плотности (KDE).
Для категориальных признаков (place_type) создает столбчатые диаграммы.
"""
print("Графики")

# Удаление технических столбцов, не представляющих интереса для анализа
clear_gpx = self.gpx_table.drop(["track_id", "point_id", "time"], axis=1)

# Получение списка всех столбцов
indices = clear_gpx.columns

# Итерация по всем признакам
for i in range(len(indices)):
# Извлечение текущего столбца
column = clear_gpx.iloc[:, i]

# Специальная обработка категориального признака place_type
if column.name == "place_type":
# Создание столбчатой диаграммы для категорий
# x указывает на столбец для группировки
sb.barplot(x="place_type", data=pd.DataFrame(column))

# plt.show без вызова () не выполняет отображение
# Это может быть опечаткой в исходном коде
plt.show
continue

# Для числовых признаков: гистограмма с KDE
# histplot строит гистограмму распределения
# kde=True добавляет сглаженную оценку плотности вероятности
sb.histplot(column, kde=True)

# Отображение графика
plt.show()

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

Метод статистического анализа

Метод statistical_distribution выполняет проверку нормальности распределений.

def statistical_distribution(self, alpha):
"""
Статистический анализ распределений числовых признаков.

Применяет тест D'Agostino-Pearson для проверки нормальности
распределения каждого числового признака.

Parameters:
alpha (float): Уровень значимости для статистических тестов
(обычно 0.05 или 0.01)
"""
print("Статистический анализ")

# Удаление технических столбцов
clear_gpx = self.gpx_table.drop(["track_id", "point_id", "time"], axis=1)

# Получение списка всех столбцов
indices = clear_gpx.columns

# Итерация по всем признакам
for i in range(len(indices)):
# Извлечение текущего столбца
column = clear_gpx.iloc[:, i]

# Пропуск категориальных признаков
# Тест нормальности применим только к числовым данным
if column.name not in ["place_type", "country"]:
# Тест D'Agostino-Pearson на нормальность распределения
# normaltest проверяет гипотезу о нормальности распределения
# Возвращает статистику теста и p-значение
stat, p = stats.normaltest(column.values)

print(f"Проверка распределения атрибутов {column.name}")

# Интерпретация результата теста
if p > alpha:
# p-значение > alpha: не можем отвергнуть H0 (нормальность)
print(f'Проверка нулевой гипотезы. alpha = {alpha}, '
f'статистика = {stat}, p-значение = {p}.\n'
f'P-значение больше alpha, что значит, что '
f'распределение нормальное.')
else:
# p-значение ≤ alpha: отвергаем H0, распределение не нормальное
print(f'Проверка нулевой гипотезы. alpha = {alpha}, '
f'статистика = {stat}, p-значение = {p}.\n'
f'P-значение меньше alpha, что значит, что '
f'распределение отклонено от нормального.')

Тест нормальности критичен для выбора дальнейших методов анализа: параметрических (при нормальном распределении) или непараметрических (при отклонении от нормальности).

Метод кластеризации

Метод clusterization выполняет снижение размерности и группировку данных.

def clusterization(self, clasters: int, table_writing = None):
"""
Кластеризация GPS-точек методом K-средних с визуализацией.

Применяет PCA для снижения размерности до 2D, затем выполняет
кластеризацию K-Means и визуализирует результаты.

Parameters:
clasters (int): Количество кластеров для K-Means
table_writing (bool, optional): Если True, добавляет метки кластеров
в основную таблицу
"""
# === Этап 1: Снижение размерности методом PCA ===
# PCA (Principal Component Analysis) - метод главных компонент
# n_components=2 - проекция на 2 главные компоненты для визуализации
pca = PCA(n_components=2)

# Удаление нечисловых столбцов перед применением PCA
# PCA требует числовых данных
data_2d = pca.fit_transform(
self.gpx_table.drop(["time", "place_type", "country"], axis=1)
)

# === Этап 2: Кластеризация методом K-средних ===
# KMeans - алгоритм кластеризации, минимизирующий внутрикластерную дисперсию
kmeans = KMeans(n_clusters=clasters)

# Обучение модели на исходных данных (не на PCA-проекции)
# Это обеспечивает кластеризацию в полном признаковом пространстве
kmeans.fit(
self.gpx_table.drop(["time", "place_type", "country"], axis=1)
)

# Предсказание меток кластеров для всех точек
labels_km = kmeans.fit_predict(
self.gpx_table.drop(["time", "place_type", "country"], axis=1)
)

# === Этап 3: Визуализация результатов кластеризации ===
# Scatter plot точек в 2D-пространстве PCA с цветовым кодированием по кластерам
plt.scatter(
data_2d[:, 0], # Первая главная компонента (ось X)
data_2d[:, 1], # Вторая главная компонента (ось Y)
c=labels_km, # Цвет точек определяется номером кластера
cmap='viridis' # Цветовая схема (от фиолетового к желтому)
)

# Подписи осей
plt.xlabel('x')
plt.ylabel('y')

# Отображение графика
plt.show()

# === Этап 4: Опциональное сохранение меток в таблицу ===
if table_writing:
# Проверка наличия столбца cluster
if "cluster" not in self.gpx_table.columns:
# Если столбца нет - добавляем новый
labels = pd.Series(labels_km)
labels.name = "cluster"
self.gpx_table = pd.concat([self.gpx_table, labels], axis=1)
else:
# Если столбец уже существует - удаляем старый и добавляем новый
# Это позволяет переобучать кластеризацию с другими параметрами
self.gpx_table = self.gpx_table.drop("cluster", axis=1)
labels = pd.Series(labels_km)
labels.name = "cluster"
self.gpx_table = pd.concat([self.gpx_table, labels], axis=1)

Комбинация PCA и K-Means является стандартным подходом для анализа многомерных данных. PCA снижает размерность для визуализации, сохраняя максимум дисперсии.

Метод оптимизации признаков

Метод data_optimization выполняет отбор информативных признаков.

def data_optimization(self, min_ratio = 0, delete_columns = False, exceptions = []):
"""
Оптимизация признакового пространства на основе важности признаков.

Использует дерево решений для оценки важности каждого признака
в задаче предсказания кластерной принадлежности. Признаки с нулевой
важностью рекомендуются к удалению.

Parameters:
min_ratio (float): Минимальный порог важности (в текущей реализации не используется)
delete_columns (bool): Если True, автоматически удаляет неинформативные признаки
exceptions (list): Список названий столбцов, которые не должны удаляться
даже при нулевой важности
"""
# Проверка наличия меток кластеров
# Метод требует предварительного выполнения кластеризации
if "cluster" in self.gpx_table.columns:
# === Этап 1: Обучение дерева решений ===
# DecisionTreeClassifier используется для оценки важности признаков
model = tree.DecisionTreeClassifier()

# Обучение модели предсказывать кластер по остальным признакам
# X - признаки (все числовые столбцы кроме целевой переменной)
# y - целевая переменная (метки кластеров)
model.fit(
self.gpx_table.drop(["cluster", "time", "place_type", "country"], axis=1),
self.gpx_table["cluster"]
)

# === Этап 2: Извлечение неинформативных признаков ===
# feature_importances_ содержит важность каждого признака (от 0 до 1)
# Признаки с важностью ≤ 0 не влияют на предсказание кластера
drop_column = self.gpx_table.drop(
["cluster", "time", "place_type", "country"], axis=1
).columns[model.feature_importances_ <= 0]

# Вывод рекомендаций по удалению
print(f"Колонки, коэффицент в которых меньше или равен 0 "
f"и которые следует удалить: {drop_column}")

# === Этап 3: Опциональное удаление неинформативных признаков ===
if delete_columns:
# Исключение защищенных столбцов из списка удаления
for i in drop_column:
if i in exceptions:
drop_column = drop_column.drop(i)

# Удаление неинформативных признаков из таблицы
self.gpx_table = self.gpx_table.drop(drop_column, axis=1)
else:
# Ошибка при отсутствии меток кластеров
print("Ошибка! Для оптимизации данных для начала проведите кластеризацию.")

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

Практическое применение

Пример использования класса

Применение класса GPXAgent демонстрирует простоту и выразительность объектно-ориентированного интерфейса.

# === Инициализация агента ===
# Параметры:
# - путь к CSV-файлу с GPX-данными
# - максимальное количество треков для обработки (1 трек для демонстрации)
agent = GPXAgent("gpx-tracks-from-hikr.org.csv", 1)

# === Обогащение данных ===
# Последовательный вызов методов добавляет новые признаки

# Добавление температурных данных для каждой точки
agent.pars_temperature()

# Добавление признаков окружающей среды
agent.pars_features()

# Вычисление количества шагов между точками
agent.pars_steps()

# Определение страны для каждой точки
agent.pars_region()

# === Анализ данных ===
# Кластеризация точек на 3 группы с сохранением меток в таблицу
agent.clusterization(3, True)

# === Оптимизация признакового пространства ===
# Удаление неинформативных признаков с защитой указанных столбцов
agent.data_optimization(
delete_columns=True,
exceptions=["track_id", "point_id", "forest_feature", "buildings_feature"]
)

# === Просмотр результатов ===
# Вывод итоговой таблицы с обработанными данными
agent.gpx_table

# === Статистический анализ ===
# Проверка нормальности распределений с уровнем значимости 0.05
agent.statistical_distribution(0.05)

Fluent interface позволяет выстраивать понятные цепочки операций обработки данных. Модульность методов обеспечивает гибкость в выборе необходимых этапов предобработки.

Обсуждение

Представленная архитектура демонстрирует преимущества объектно-ориентированного подхода к автоматизации предобработки геопространственных данных. Ключевые особенности реализации включают:

  • Инкапсуляция состояния: Класс объединяет данные (gpx_table) и методы их обработки, что упрощает управление сложным конвейером.
  • Модульность: Каждый метод выполняет специфическую задачу, что облегчает тестирование, отладку и расширение функциональности.
  • Устойчивость к ошибкам: Обработка исключений в критических операциях (загрузка данных, API-запросы) обеспечивает надежность системы.
  • Расширяемость: Архитектура позволяет легко добавлять новые методы обработки без модификации существующего кода.

Основные направления развития системы включают:

  • Асинхронная обработка: Использование asyncio для параллельных API-запросов повысит производительность.
  • Кеширование: Сохранение результатов геокодирования и запросов к OSM сократит время обработки.
  • Конфигурируемость: Вынесение параметров (радиус поиска, длина шага) в конфигурационный файл.
  • Валидация: Добавление методов проверки качества данных на каждом этапе обработки.

Заключение

Данная работа представляет объектно-ориентированную систему автоматизированной обработки GPS-треков, реализованную в виде класса GPXAgent. Архитектура обеспечивает полный цикл предобработки: от парсинга исходных данных до оптимизации признакового пространства. Модульная структура класса позволяет гибко комбинировать методы обработки в зависимости от требований задачи.

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

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