ЭТАЛОН ВЫПОЛНЕНИЯ ЛАБОРАТОРНОЙ РАБОТЫ №3 (ЛР3): ЧИСЛЕННЫЙ АНАЛИЗ ДАННЫХ С ИСПОЛЬЗОВАНИЕМ NUMPY
Файл:
lab3_numpy.py
Данные: data/spotify_edu_dataset.csv
"""
ЛР3. Численный анализ данных с использованием NumPy (эталонный
каркас).
Назначение:
- показать рекомендуемую архитектуру решения;
- продемонстрировать формирование массивов NumPy из CSV;
- обеспечить корректную работу с пропусками (np.nan);
- заложить основу для этапов 2–6 (статистика, маски, корреляция,
масштабирование, время).
Требование:
- при использовании эталона обучающийся должен понимать логику каждого шага
и отразить в отчёте собственные результаты и интерпретации.
"""
from __future__ import annotations
import csv
import time
from typing import Dict, List, Tuple
import numpy as np
# -------------------------
# Константы и настройки
# -------------------------
DATA_PATH = "data/spotify_edu_dataset.csv"
NUMERIC_COLUMNS = [
"duration_sec",
"energy",
"danceability",
"tempo",
"views",
]
# -------------------------
# Вспомогательные функции
# -------------------------
def to_float_or_nan(value: str) -> float:
"""
Преобразует строковое значение в float.
Возвращает np.nan, если
значение пустое или некорректное.
Примеры некорректности:
- пустая строка
- нечисловой текст
- пробелы и т.п.
"""
if value is None:
return np.nan
s = str(value).strip()
if s == "":
return np.nan
try:
return float(s)
except ValueError:
return np.nan
def read_csv_as_dicts(path: str) -> List[Dict[str, str]]:
"""
Считывает CSV в виде списка словарей.
Ключи словаря – имена столбцов (из заголовка CSV).
Значения – строки (как в файле).
"""
rows: List[Dict[str, str]] = []
with open(path, "r", encoding="utf-8",
newline="") as f:
reader = csv.DictReader(f)
for row in reader:
rows.append(row)
return rows
def build_numeric_arrays(rows: List[Dict[str, str]], columns: List[str]) ->
Dict[str, np.ndarray]:
"""
Извлекает указанные числовые столбцы и формирует для каждого np.ndarray
dtype=float.
Пропуски/ошибки преобразования заменяются на np.nan.
"""
arrays: Dict[str, np.ndarray] = {}
for col in columns:
values = [to_float_or_nan(r.get(col, "")) for r in rows]
arrays[col] = np.array(values, dtype=float)
return arrays
def check_array(arr: np.ndarray, name: str) -> None:
"""
Выводит минимально необходимые проверки:
- dtype
- shape
- число пропусков (np.nan)
"""
nan_count = int(np.isnan(arr).sum())
print(f"[CHECK] {name}: dtype={arr.dtype}, shape={arr.shape},
NaN={nan_count}")
# -------------------------
# Этап 2: статистика (заготовки)
# -------------------------
def basic_stats(arr: np.ndarray) -> Dict[str, float]:
"""
Базовые статистики с игнорированием NaN там, где это необходимо.
"""
return {
"mean": float(np.nanmean(arr)),
"median": float(np.nanmedian(arr)),
"std": float(np.nanstd(arr)),
"min": float(np.nanmin(arr)),
"max": float(np.nanmax(arr)),
}
# -------------------------
# Этап 3: булевы маски (заготовки)
# -------------------------
def count_where(arr: np.ndarray, condition: np.ndarray) -> int:
"""
Количество элементов, удовлетворяющих условию (маске).
"""
# condition – булев массив True/False той же формы, что и arr
return int(condition.sum())
# -------------------------
# Этап 4: корреляция (заготовки)
# -------------------------
def corr_pearson(x: np.ndarray, y: np.ndarray) -> float:
"""
Корреляция Пирсона между двумя массивами, с удалением пар NaN.
"""
mask = ~np.isnan(x) & ~np.isnan(y)
x_clean = x[mask]
y_clean = y[mask]
# Если слишком мало данных, корреляция может быть неустойчивой
if x_clean.size < 2:
return float("nan")
r_matrix = np.corrcoef(x_clean, y_clean)
return float(r_matrix[0, 1])
# -------------------------
# Этап 5: масштабирование (заготовки)
# -------------------------
def standardize(x: np.ndarray) -> np.ndarray:
"""
Стандартизация: z = (x - mean) / std (с игнорированием NaN).
"""
mu = np.nanmean(x)
sigma = np.nanstd(x)
return (x - mu) / sigma
def minmax_normalize(x: np.ndarray) -> np.ndarray:
"""
Min–max нормализация в диапазон [0, 1] (с игнорированием NaN).
"""
x_min = np.nanmin(x)
x_max = np.nanmax(x)
return (x - x_min) / (x_max - x_min)
# -------------------------
# Этап 6: производительность (заготовки)
# -------------------------
def mean_by_loop(arr: np.ndarray) -> float:
"""
Среднее по циклу Python с игнорированием NaN.
"""
total = 0.0
count = 0
for v in arr:
if not np.isnan(v):
total += float(v)
count += 1
return total / count if count else float("nan")
def time_call(fn, *args, repeats: int = 5) -> Tuple[float, float]:
"""
Измеряет время выполнения функции fn(*args).
Возвращает (минимум, среднее) по нескольким запускам.
"""
times = []
for _ in range(repeats):
t0 = time.time()
fn(*args)
t1 = time.time()
times.append(t1 - t0)
return min(times), sum(times) / len(times)
# -------------------------
# main
# -------------------------
def main() -> None:
# ЭТАП 1. Чтение CSV и формирование массивов
rows = read_csv_as_dicts(DATA_PATH)
arrays = build_numeric_arrays(rows, NUMERIC_COLUMNS)
print(f"Загружено записей: {len(rows)}")
for name, arr in arrays.items():
check_array(arr, name)
# Ниже – примеры вызовов для следующих этапов (можно раскомментировать по
заданию)
# ЭТАП 2. Базовая статистика
# for name, arr in arrays.items():
# stats = basic_stats(arr)
# print(name, stats)
# ЭТАП 3. Маски
# energy = arrays["energy"]
# n_high_energy = count_where(energy, energy > 0.8)
# print("energy > 0.8:", n_high_energy)
# ЭТАП 4. Корреляция
# r = corr_pearson(arrays["energy"],
arrays["danceability"])
# print("corr(energy, danceability) =", r)
# ЭТАП 5. Масштабирование
# z = standardize(arrays["views"])
# x_norm = minmax_normalize(arrays["views"])
# print("views standardized: ", z[:5])
# print("views normalized: ", x_norm[:5])
# ЭТАП 6. Производительность
# arr = arrays["duration_sec"]
# tmin_loop, tavg_loop = time_call(mean_by_loop, arr)
# tmin_np, tavg_np = time_call(np.nanmean, arr)
# print("Loop mean time (min/avg):", tmin_loop, tavg_loop)
# print("NumPy mean time (min/avg):", tmin_np, tavg_np)
if __name__ == "__main__":
main()