378 KiB
Датасет №1: Объекты вокруг Земли.¶
Описание датасета:¶
Данный набор данных представляет собой коллекцию сведений о ближайших к Земле объектах (астероидах), сертифицированных NASA. Он содержит данные, которые могут помочь идентифицировать потенциально опасные астероиды, которые могут оказать влияние на Землю или на космические миссии. Набор данных включает в себя такие ключевые характеристики астероидов, как их размер, скорость, расстояние до Земли и информация о возможной опасности столкновения.
Анализ сведений:¶
Проблемная область: Основной проблемной областью является отслеживание и оценка рисков, связанных с приближением астероидов к Земле. С помощью данных о движении и характеристиках астероидов можно предсказать возможные столкновения и минимизировать угрозу для Земли, планируя превентивные действия.
Актуальность: Набор данных высокоактуален для задач оценки рисков от космических объектов, мониторинга космического пространства и разработки превентивных мер по защите Земли. Также он важен для научных исследований в области астрономии и планетарной безопасности.
Объекты наблюдения: Объектами наблюдения в данном наборе данных являются астероиды, классифицированные NASA как "ближайшие к Земле объекты" (Near-Earth Objects, NEO). Эти объекты могут проходить в непосредственной близости от Земли, что потенциально представляет опасность.
Атрибуты объектов:
- id: Уникальный идентификатор астероида.
- name: Название, присвоенное астероиду NASA.
- est_diameter_min: Минимальный оценочные диаметры астероида в километрах.
- est_diameter_max: Максимальный оценочные диаметры астероида в километрах.
- relative_velocity: Скорость астероида относительно Земли (в км/с).
- miss_distance: Расстояние, на котором астероид пролетел мимо Земли, в километрах.
- orbiting_body: Планета, вокруг которой вращается астероид.
- sentry_object: Признак, указывающий на наличие астероида в системе автоматического мониторинга столкновений (система Sentry).
- absolute_magnitude: Абсолютная величина, описывающая яркость объекта.
- hazardous: Булев признак, указывающий, является ли астероид потенциально опасным.
Связь между объектами: В данном наборе данных отсутствует явная связь между астероидами, однако на основе орбитальных параметров можно исследовать группы объектов, имеющие схожие орбиты или величины риска столкновения с Землей.
Качество набора данных:¶
Информативность: Датасет предоставляет важные сведения о ключевых характеристиках астероидов, такие как размер, скорость и расстояние от Земли, что позволяет проводить качественный анализ их потенциальной опасности.
Степень покрытия: Набор данных включает данные о большом количестве астероидов (>90000 записей), что позволяет охватить значительную часть ближайших к Земле объектов. Однако не все астероиды могут быть обнаружены, так как данные зависят от возможности их наблюдения.
Соответствие реальным данным: Данные в наборе предоставлены NASA, что указывает на высокую достоверность и актуальность информации. Тем не менее, параметры, такие как диаметр и расстояние, могут быть оценочными и подвергаться уточнению с новыми наблюдениями.
Согласованность меток: Метрики в датасете четко обозначены, а булевы признаки, такие как "hazardous" (опасен или нет), соответствуют конкретным параметрам астероидов и легко интерпретируются.
Бизес-цели:¶
- Мониторинг космических угроз: Создание системы, которая анализирует астероиды и предсказывает риски столкновения с Землей, помогая государственным агентствам и частным компаниям разрабатывать превентивные меры.
- Поддержка космических миссий: Предоставление точных данных для планирования и безопасного проведения космических миссий, минимизация рисков столкновения с космическими объектами.
- Образовательные и научные исследования: Использование данных для поддержки образовательных программ и научных исследований в области астрономии и космической безопасности.
Эффект для бизнеса: Набор данных способствует развитию технологий космической безопасности, минимизирует финансовые риски от потенциальных катастроф и поддерживает стратегическое планирование космических миссий.
Технические цели:¶
- Моделирование риска столкновения: Построение алгоритмов машинного обучения для прогнозирования вероятности столкновения астероидов с Землей.
- Анализ и кластеризация астероидов: Исследование взаимосвязей между астероидами, анализ орбитальных данных и выделение групп астероидов, имеющих схожие характеристики.
- Оптимизация системы предупреждения угроз: Создание системы раннего оповещения, которая будет автоматически анализировать данные и предупреждать о потенциальных угрозах в реальном времени.
Входные данные: Диаметр, скорость, расстояние, орбитальные параметры астероидов.
Целевой признак: Признак "hazardous" – бинарная метка, указывающая на потенциальную опасность астероида.
Выгрузка данных из файла в DataFrame:¶
from typing import Any
from math import ceil
import pandas as pd
from pandas import DataFrame, Series
from sklearn.model_selection import train_test_split
from imblearn.over_sampling import ADASYN
from imblearn.under_sampling import RandomUnderSampler
import matplotlib.pyplot as plt
df: DataFrame = pd.read_csv('..//static//csv//neo.csv')
Краткая информация о DataFrame:¶
# Краткая информация о DataFrame
df.info()
# Статистическое описание числовых столбцов
df.describe()
Проблема пропущенных данных:¶
Проблема пропущенных данных — это отсутствие значений в наборе данных, что может искажать результаты анализа и статистические выводы.
Проверка на отсутствие значений, представленная ниже, показала, что DataFrame не имеет пустых значений признаков. Нет необходимости использовать методы заполнения пропущенных данных.
# Проверка пропущенных данных
def check_null_columns(dataframe: DataFrame) -> None:
# Присутствуют ли пустые значения признаков
print(dataframe.isnull().any(), '\n')
# Количество пустых значений признаков
print(dataframe.isnull().sum())
# Процент пустых значений признаков
for i in dataframe.columns:
null_rate: float = dataframe[i].isnull().sum() / len(dataframe) * 100
if null_rate > 0:
print(f"{i} процент пустых значений: %{null_rate:.2f}")
# Проверка пропущенных данных
check_null_columns(df)
Проблема зашумленности данных:¶
Зашумленность – это наличие случайных ошибок или вариаций в данных, которые могут затруднить выявление истинных закономерностей. Шум может возникать из-за ошибок измерений, неправильных записей или других факторов.
Выбросы – это значения, которые значительно отличаются от остальных наблюдений в наборе данных. Выбросы могут указывать на ошибки в данных или на редкие, но важные события. Их наличие может повлиять на статистические методы анализа.
Представленный ниже код помогает определить наличие выбросов в наборе данных и устранить их (при наличии), заменив значения ниже нижней границы (рассматриваемого минимума) на значения нижней границы, а значения выше верхней границы (рассматриваемого максимума) – на значения верхней границы.
# Числовые столбцы DataFrame
numeric_columns: list[str] = [
'est_diameter_min',
'est_diameter_max',
'relative_velocity',
'miss_distance',
'absolute_magnitude'
]
# Проверка выбросов в DataFrame
def check_outliers(dataframe: DataFrame, columns: list[str]) -> None:
for column in columns:
if not pd.api.types.is_numeric_dtype(dataframe[column]): # Проверяем, является ли колонка числовой
continue
Q1: float = dataframe[column].quantile(0.25) # 1-й квартиль (25%)
Q3: float = dataframe[column].quantile(0.75) # 3-й квартиль (75%)
IQR: float = Q3 - Q1 # Вычисляем межквартильный размах
# Определяем границы для выбросов
lower_bound: float = Q1 - 1.5 * IQR # Нижняя граница
upper_bound: float = Q3 + 1.5 * IQR # Верхняя граница
# Подсчитываем количество выбросов
outliers: DataFrame = dataframe[(dataframe[column] < lower_bound) | (dataframe[column] > upper_bound)]
outlier_count: int = outliers.shape[0]
print(f"Колонка {column}:")
print(f"\tЕсть выбросы: {'Да' if outlier_count > 0 else 'Нет'}")
print(f"\tКоличество выбросов: {outlier_count}")
print(f"\tМинимальное значение: {dataframe[column].min()}")
print(f"\tМаксимальное значение: {dataframe[column].max()}")
print(f"\t1-й квартиль (Q1): {Q1}")
print(f"\t3-й квартиль (Q3): {Q3}\n")
# Визуализация выбросов
def visualize_outliers(dataframe: DataFrame, columns: list[str]) -> None:
# Диаграммы размахов
plt.figure(figsize=(15, 10))
rows: int = ceil(len(columns) / 3)
for index, column in enumerate(columns, 1):
plt.subplot(rows, 3, index)
plt.boxplot(dataframe[column], vert=True, patch_artist=True)
plt.title(f"Диаграмма размахов для \"{column}\"")
plt.xlabel(column)
# Отображение графиков
plt.tight_layout()
plt.show()
# Проверка выбросов
check_outliers(df, numeric_columns)
visualize_outliers(df, numeric_columns)
# Устранить выборсы в DataFrame
def remove_outliers(dataframe: DataFrame, columns: list[str]) -> DataFrame:
for column in columns:
if not pd.api.types.is_numeric_dtype(dataframe[column]): # Проверяем, является ли колонка числовой
continue
Q1: float = dataframe[column].quantile(0.25) # 1-й квартиль (25%)
Q3: float = dataframe[column].quantile(0.75) # 3-й квартиль (75%)
IQR: float = Q3 - Q1 # Вычисляем межквартильный размах
# Определяем границы для выбросов
lower_bound: float = Q1 - 1.5 * IQR # Нижняя граница
upper_bound: float = Q3 + 1.5 * IQR # Верхняя граница
# Устраняем выбросы:
# Заменяем значения ниже нижней границы на нижнюю границу
# А значения выше верхней границы – на верхнюю
dataframe[column] = dataframe[column].apply(lambda x: lower_bound if x < lower_bound else upper_bound if x > upper_bound else x)
return dataframe
# Устраняем выборсы
df: DataFrame = remove_outliers(df, numeric_columns)
# Проверка выбросов
check_outliers(df, numeric_columns)
visualize_outliers(df, numeric_columns)
Разбиение набора данных на выборки:¶
Групповое разбиение данных – это метод разделения данных на несколько групп или подмножеств на основе определенного признака или характеристики. При этом наблюдения для одного объекта должны попасть только в одну выборку.
Основные виды выборки данных:
- Обучающая выборка (60-80%). Обучение модели (подбор коэффициентов некоторой математической функции для аппроксимации).
- Контрольная выборка (10-20%). Выбор метода обучения, настройка гиперпараметров.
- Тестовая выборка (10-20% или 20-30%). Оценка качества модели перед передачей заказчику.
Разделим выборку данных на 3 группы и проанализируем качество распределения данных.
Весь набор данных состоит из 90836 объектов, из которых 81996 (около 90.3%) неопасны (False), а 8840 (около 9.7%) опасны (True). Это говорит о том, что класс "неопасные" значительно преобладает.
Все выборки показывают одинаковое распределение классов, что свидетельствует о том, что данные были отобраны случайным образом и не содержат явного смещения.
Однако, несмотря на сбалансированность при разбиении данных, в целом данные обладают значительным дисбалансом между классами. Это может быть проблемой при обучении модели, так как она может иметь тенденцию игнорировать опасные объекты (True), что следует учитывать при дальнейшем анализе и выборе методов обработки данных.
Для получения более сбалансированных выборок данных необходимо воспользоваться методами приращения (аугментации) данных, а именно методами oversampling и undersampling.
# Функция для создания выборок
def split_stratified_into_train_val_test(
df_input,
stratify_colname="y",
frac_train=0.6,
frac_val=0.15,
frac_test=0.25,
random_state=None,
) -> tuple[Any, Any, Any]:
"""
Splits a Pandas dataframe into three subsets (train, val, and test)
following fractional ratios provided by the user, where each subset is
stratified by the values in a specific column (that is, each subset has
the same relative frequency of the values in the column). It performs this
splitting by running train_test_split() twice.
Parameters
----------
df_input : Pandas dataframe
Input dataframe to be split.
stratify_colname : str
The name of the column that will be used for stratification. Usually
this column would be for the label.
frac_train : float
frac_val : float
frac_test : float
The ratios with which the dataframe will be split into train, val, and
test data. The values should be expressed as float fractions and should
sum to 1.0.
random_state : int, None, or RandomStateInstance
Value to be passed to train_test_split().
Returns
-------
df_train, df_val, df_test :
Dataframes containing the three splits.
"""
if frac_train + frac_val + frac_test != 1.0:
raise ValueError(
"fractions %f, %f, %f do not add up to 1.0"
% (frac_train, frac_val, frac_test)
)
if stratify_colname not in df_input.columns:
raise ValueError("%s is not a column in the dataframe" % (stratify_colname))
X: DataFrame = df_input # Contains all columns.
y: DataFrame = df_input[
[stratify_colname]
] # Dataframe of just the column on which to stratify.
# Split original dataframe into train and temp dataframes.
df_train, df_temp, y_train, y_temp = train_test_split(
X, y,
stratify=y,
test_size=(1.0 - frac_train),
random_state=random_state
)
# Split the temp dataframe into val and test dataframes.
relative_frac_test: float = frac_test / (frac_val + frac_test)
df_val, df_test, y_val, y_test = train_test_split(
df_temp,
y_temp,
stratify=y_temp,
test_size=relative_frac_test,
random_state=random_state,
)
assert len(df_input) == len(df_train) + len(df_val) + len(df_test)
return df_train, df_val, df_test
# Вывод распределения количества наблюдений по меткам (классам)
print(df.hazardous.value_counts(), '\n')
data: DataFrame = df[[
'est_diameter_min',
'est_diameter_max',
'relative_velocity',
'miss_distance',
'absolute_magnitude',
'hazardous'
]].copy()
df_train, df_val, df_test = split_stratified_into_train_val_test(
data,
stratify_colname="hazardous",
frac_train=0.60,
frac_val=0.20,
frac_test=0.20
)
# Оценка сбалансированности
def check_balance(dataframe: DataFrame, dataframe_name: str, column: str) -> None:
counts: Series[int] = dataframe[column].value_counts()
print(dataframe_name + ": ", dataframe.shape)
print(f"Распределение выборки данных по классам \"{column}\":\n", counts)
total_count: int = len(dataframe)
for value in counts.index:
percentage: float = counts[value] / total_count * 100
print(f"Процент объектов класса \"{value}\": {percentage:.2f}%")
print()
# Определение необходимости аугментации данных
def need_augmentation(dataframe: DataFrame,
column: str,
first_value: Any, second_value: Any) -> bool:
counts: Series[int] = dataframe[column].value_counts()
ratio: float = counts[first_value] / counts[second_value]
return ratio > 1.5 or ratio < 0.67
# Визуализация сбалансированности классов
def visualize_balance(dataframe_train: DataFrame,
dataframe_val: DataFrame,
dataframe_test: DataFrame,
column: str) -> None:
fig, axes = plt.subplots(1, 3, figsize=(15, 5))
# Обучающая выборка
counts_train: Series[int] = dataframe_train[column].value_counts()
axes[0].pie(counts_train, labels=counts_train.index, autopct='%1.1f%%', startangle=90)
axes[0].set_title(f"Распределение классов \"{column}\" в обучающей выборке")
# Контрольная выборка
counts_val: Series[int] = dataframe_val[column].value_counts()
axes[1].pie(counts_val, labels=counts_val.index, autopct='%1.1f%%', startangle=90)
axes[1].set_title(f"Распределение классов \"{column}\" в контрольной выборке")
# Тестовая выборка
counts_test: Series[int] = dataframe_test[column].value_counts()
axes[2].pie(counts_test, labels=counts_test.index, autopct='%1.1f%%', startangle=90)
axes[2].set_title(f"Распределение классов \"{column}\" в тренировочной выборке")
# Отображение графиков
plt.tight_layout()
plt.show()
# Проверка сбалансированности
check_balance(df_train, 'Обучающая выборка', 'hazardous')
check_balance(df_val, 'Контрольная выборка', 'hazardous')
check_balance(df_test, 'Тестовая выборка', 'hazardous')
# Проверка необходимости аугментации
print(f"Для обучающей выборки аугментация данных {'не ' if not need_augmentation(df_train, 'hazardous', True, False) else ''}требуется")
print(f"Для контрольной выборки аугментация данных {'не ' if not need_augmentation(df_val, 'hazardous', True, False) else ''}требуется")
print(f"Для тестовой выборки аугментация данных {'не ' if not need_augmentation(df_test, 'hazardous', True, False) else ''}требуется")
# Визуализация сбалансированности классов
visualize_balance(df_train, df_val, df_test, 'hazardous')
Приращение данных:¶
Аугментация данных может быть полезна в том случае, когда имеется недостаточное количество данных и мы хотим сгенерировать новые данные на основе имеющихся, слегка модифицировав их.
Методы решения:
- Выборка с избытком (oversampling). Копирование наблюдений или генерация новых наблюдений на основе существующих с помощью алгоритмов SMOTE и ADASYN (нахождение k-ближайших соседей).
- Выборка с недостатком (undersampling). Исключение некоторых наблюдений для меток с большим количеством наблюдений. Наблюдения можно исключать случайным образом или на основе определения связей Томека для наблюдений разных меток.
# Метод приращения с избытком (oversampling)
def oversample(df: DataFrame, column: str) -> DataFrame:
X: DataFrame = df.drop(column, axis=1)
y: DataFrame = df[column] # type: ignore
adasyn = ADASYN()
X_resampled, y_resampled = adasyn.fit_resample(X, y) # type: ignore
df_resampled: DataFrame = pd.concat([X_resampled, y_resampled], axis=1)
return df_resampled
# Приращение данных (oversampling)
df_train_oversampled: DataFrame = oversample(df_train, 'hazardous')
df_val_oversampled: DataFrame = oversample(df_val, 'hazardous')
df_test_oversampled: DataFrame = oversample(df_test, 'hazardous')
# Проверка сбалансированности
print('После применения метода oversampling:')
check_balance(df_train_oversampled, 'Обучающая выборка', 'hazardous')
check_balance(df_val_oversampled, 'Контрольная выборка', 'hazardous')
check_balance(df_test_oversampled, 'Тестовая выборка', 'hazardous')
# Проверка необходимости аугментации
print(f"Для обучающей выборки аугментация данных {'не ' if not need_augmentation(df_train_oversampled, 'hazardous', True, False) else ''}требуется")
print(f"Для контрольной выборки аугментация данных {'не ' if not need_augmentation(df_val_oversampled, 'hazardous', True, False) else ''}требуется")
print(f"Для тестовой выборки аугментация данных {'не ' if not need_augmentation(df_test_oversampled, 'hazardous', True, False) else ''}требуется")
# Визуализация сбалансированности классов
visualize_balance(df_train_oversampled, df_val_oversampled, df_test_oversampled, 'hazardous')
# Метод приращения с недостатком (undersampling)
def undersample(df: DataFrame, column: str) -> DataFrame:
X: DataFrame = df.drop(column, axis=1)
y: DataFrame = df[column] # type: ignore
undersampler = RandomUnderSampler()
X_resampled, y_resampled = undersampler.fit_resample(X, y) # type: ignore
df_resampled: DataFrame = pd.concat([X_resampled, y_resampled], axis=1)
return df_resampled
# Приращение данных (undersampling)
df_train_undersampled: DataFrame = undersample(df_train, 'hazardous')
df_val_undersampled: DataFrame = undersample(df_val, 'hazardous')
df_test_undersampled: DataFrame = undersample(df_test, 'hazardous')
# Проверка сбалансированности
print('После применения метода undersampling:')
check_balance(df_train_undersampled, 'Обучающая выборка', 'hazardous')
check_balance(df_val_undersampled, 'Контрольная выборка', 'hazardous')
check_balance(df_test_undersampled, 'Тестовая выборка', 'hazardous')
# Проверка необходимости аугментации
print(f"Для обучающей выборки аугментация данных {'не ' if not need_augmentation(df_train_undersampled, 'hazardous', True, False) else ''}требуется")
print(f"Для контрольной выборки аугментация данных {'не ' if not need_augmentation(df_val_undersampled, 'hazardous', True, False) else ''}требуется")
print(f"Для тестовой выборки аугментация данных {'не ' if not need_augmentation(df_test_undersampled, 'hazardous', True, False) else ''}требуется")
# Визуализация сбалансированности классов
visualize_balance(df_train_undersampled, df_val_undersampled, df_test_undersampled, 'hazardous')