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())       # пропуски по столбцам
Аргумент decimal=',' обязателен для русских данных: без него столбец «12,5» прочитается как текст, и любая арифметика по нему упадёт. Проверка df.info() сразу покажет, где вместо float оказался object.

Отбор данных

# По столбцам
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)
После merge всегда сравнивайте число строк. Их рост означает дубликаты в ключе справочника, а появление пропусков — что часть записей не нашла пары. Обе ситуации молча искажают все дальнейшие расчёты.

Временные ряды

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 — и это заодно короче читается.

Читайте также

Сделаем работу по этой теме

Опишите задачу — ответим в течение 15 минут в личных сообщениях ВКонтакте, назовём срок и цену. Предоплаты за оценку нет.

  • Оценка заявки бесплатно
  • Правки по замечаниям преподавателя
  • Работы по всем техническим и IT-дисциплинам

Нажимая кнопку, вы соглашаетесь на обработку указанных данных для ответа на заявку.