Python: pandas и анализ данных
Чтение CSV и Excel в pandas, отбор и фильтрация данных, обработка пропусков, группировка и сводные таблицы, объединение датафреймов, построение графиков.
Практическая часть курсовой по анализу данных почти всегда состоит из одних и тех же шагов: загрузить, почистить, сгруппировать, построить график, сделать вывод. Разберём каждый.
Загрузка данных
import pandas as pd
import numpy as np
# Русские CSV часто с точкой с запятой и в cp1251
df = pd.read_csv('data.csv', sep=';', encoding='utf-8-sig', decimal=',')
df = pd.read_excel('report.xlsx', sheet_name='Данные', header=2)
# Первый взгляд на данные
print(df.shape) # (строк, столбцов)
print(df.head())
print(df.info()) # типы и пропуски
print(df.describe()) # статистика по числовым столбцам
print(df.isna().sum()) # пропуски по столбцам
Отбор данных
# По столбцам
df['цена'] # один столбец (Series)
df[['товар', 'цена']] # несколько (DataFrame)
# По условию
df[df['цена'] > 1000]
df[(df['цена'] > 1000) & (df['категория'] == 'электроника')]
df[df['товар'].str.contains('резистор', case=False, na=False)]
df[df['категория'].isin(['электроника', 'крепёж'])]
# По позиции и метке
df.iloc[0] # первая строка по номеру
df.loc[df['цена'].idxmax()] # строка с максимальной ценой
df.loc[df['цена'] > 1000, 'скидка'] = 0.1 # запись по условию
# Сортировка
df.sort_values(['категория', 'цена'], ascending=[True, False])
df.nlargest(5, 'цена') # топ-5, короче чем sort + head
Очистка
# Пропуски
df['цена'] = df['цена'].fillna(df['цена'].median()) # медианой устойчивее среднего
df = df.dropna(subset=['товар']) # без названия строка бесполезна
# Дубликаты
print('дубликатов:', df.duplicated().sum())
df = df.drop_duplicates()
# Типы
df['дата'] = pd.to_datetime(df['дата'], format='%d.%m.%Y', errors='coerce')
df['цена'] = pd.to_numeric(df['цена'], errors='coerce')
# Текст
df['товар'] = df['товар'].str.strip().str.lower()
# Выбросы по правилу межквартильного размаха
q1, q3 = df['цена'].quantile([0.25, 0.75])
iqr = q3 - q1
mask = df['цена'].between(q1 - 1.5*iqr, q3 + 1.5*iqr)
print(f'выбросов: {(~mask).sum()}')
df_clean = df[mask]
Выбросы не удаляйте молча: в отчёте укажите, сколько строк отброшено и почему. Иногда именно выбросы — самое интересное в данных, и их исключение придётся обосновать.
Группировка и сводные таблицы
# Одна агрегация
df.groupby('категория')['цена'].mean()
# Несколько сразу
stats = df.groupby('категория').agg(
количество=('товар', 'count'),
средняя=('цена', 'mean'),
медиана=('цена', 'median'),
сумма=('выручка', 'sum'),
разброс=('цена', 'std'),
).round(2).sort_values('сумма', ascending=False)
print(stats)
# Сводная таблица
pivot = df.pivot_table(
index='категория',
columns='месяц',
values='выручка',
aggfunc='sum',
fill_value=0,
margins=True, # добавит итоги
)
# Доля от общего
df['доля'] = df['выручка'] / df['выручка'].sum() * 100
# Оконные функции
df['ранг_в_категории'] = df.groupby('категория')['цена'].rank(ascending=False)
df['накопленная'] = df.sort_values('дата')['выручка'].cumsum()
Объединение таблиц
# Как SQL JOIN
merged = orders.merge(products, on='product_id', how='left')
merged = orders.merge(customers, left_on='client_id', right_on='id', how='inner')
# Проверка после объединения — обязательный шаг
print('строк до:', len(orders), 'после:', len(merged))
print('не нашли соответствие:', merged['product_name'].isna().sum())
# Склейка по строкам
all_data = pd.concat([jan, feb, mar], ignore_index=True)
Временные ряды
df = df.set_index('дата').sort_index()
monthly = df['выручка'].resample('MS').sum() # по месяцам
monthly_avg = df['выручка'].resample('W').mean() # по неделям
# Скользящее среднее сглаживает шум
df['ma7'] = df['выручка'].rolling(window=7, min_periods=1).mean()
# Прирост к предыдущему периоду
monthly_growth = monthly.pct_change() * 100
# Отбор по периоду
q1 = df.loc['2026-01':'2026-03']
Графики
import matplotlib.pyplot as plt
plt.rcParams['font.family'] = 'DejaVu Sans' # чтобы кириллица не превратилась в квадраты
fig, axes = plt.subplots(1, 2, figsize=(12, 4.5))
stats['сумма'].plot.barh(ax=axes[0], color='#0969da')
axes[0].set_title('Выручка по категориям')
axes[0].set_xlabel('руб.')
monthly.plot(ax=axes[1], marker='o')
axes[1].set_title('Динамика по месяцам')
axes[1].grid(alpha=0.3)
plt.tight_layout()
plt.savefig('analysis.png', dpi=150, bbox_inches='tight')
Корреляция и простая модель
# Матрица корреляций
corr = df[['цена', 'количество', 'выручка', 'скидка']].corr().round(2)
print(corr)
# Линейная регрессия
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import r2_score, mean_absolute_error
X = df[['цена', 'скидка']]
y = df['количество']
X_tr, X_te, y_tr, y_te = train_test_split(X, y, test_size=0.25, random_state=42)
model = LinearRegression().fit(X_tr, y_tr)
pred = model.predict(X_te)
print('коэффициенты:', dict(zip(X.columns, model.coef_.round(3))))
print('R² =', round(r2_score(y_te, pred), 3))
print('MAE =', round(mean_absolute_error(y_te, pred), 2))
Корреляция не означает причинно-следственную связь — эту оговорку в отчёте ждут. И обязательно разделяйте выборку на обучающую и тестовую: R² на тех же данных, на которых модель обучалась, ничего не доказывает.
Выгрузка результатов
with pd.ExcelWriter('результаты.xlsx', engine='openpyxl') as w:
df_clean.to_excel(w, sheet_name='Данные', index=False)
stats.to_excel(w, sheet_name='Статистика')
pivot.to_excel(w, sheet_name='Сводная')
stats.to_csv('stats.csv', sep=';', decimal=',', encoding='utf-8-sig')
print(stats.to_markdown()) # готовая таблица для вставки в отчёт
Частые вопросы
Чем loc отличается от iloc?
loc обращается по метке индекса и по имени столбца, iloc — строго по числовой позиции. При целочисленном индексе они выглядят похоже, но loc включает правую границу срезa, а iloc — нет.
Почему появляется предупреждение SettingWithCopyWarning?
Вы пытаетесь записать значение в срез, который может быть копией. Правильно писать через df.loc[условие, 'столбец'] = значение, а не df[условие]['столбец'] = значение.
Стоит ли использовать циклы по строкам?
Нет, iterrows работает в десятки раз медленнее векторных операций. Почти любую задачу можно выразить через операции над столбцами, groupby или apply — и это заодно короче читается.