В мире анализа данных сложно переоценить важность эффективных инструментов для работы с большими массивами информации. В этой статье я хочу поделиться своим опытом использования двух фундаментальных библиотек Python для анализа данных: NumPy и Pandas, а также рассказать о техниках дескриптивного анализа, которые позволяют извлечь максимум полезной информации из имеющихся данных.
Фишки NumPy: высокопроизводительные вычисления с массивами
NumPy (Numerical Python) – это фундаментальная библиотека для научных вычислений в Python, без которой сложно представить современный анализ данных.
Ее центральным элементом является объект ndarray – n-мерный массив, обеспечивающий высокопроизводительные операции с большими объемами данных. Работая с различными проектами, я постоянно убеждаюсь в том, насколько эффективнее становится код при использовании векторизованных операций NumPy вместо стандартных циклов Python.
Структура и особенности ndarray
Массивы NumPy существенно отличаются от стандартных списков Python, и понимание этих различий критически важно для эффективной работы с данными. В отличие от списков, которые могут содержать элементы разных типов и хранятся разрозненно в памяти, ndarray – это гомогенная структура с фиксированным размером, где все элементы имеют одинаковый тип данных и располагаются последовательно в памяти.
Такая организация обеспечивает несколько ключевых преимуществ:
- Эффективное использование памяти – отсутствие избыточных метаданных для каждого элемента;
- Высокая скорость доступа к элементам благодаря их последовательному расположению;
- Возможность применения оптимизированных низкоуровневых функций для массовых операций;
- Легкая интеграция с кодом на C/C++ через механизм буферов памяти.
Приведу пример создания и базовых операций с массивами NumPy:
import numpy as np
# Создание массивов разными способами
arr1 = np.array([1, 2, 3, 4, 5]) # Из списка
arr2 = np.zeros((3, 4)) # Заполненный нулями массив размера 3x4
arr3 = np.ones((2, 3, 4)) # Трехмерный массив, заполненный единицами
arr4 = np.arange(0, 10, 0.5) # Массив с равномерно распределенными значениями
arr5 = np.linspace(0, 1, 5) # 5 равномерно распределенных точек от 0 до 1
arr6 = np.random.random((2, 2)) # Случайные числа в диапазоне [0, 1)
# Базовые атрибуты массива
print(f"Форма массива: {arr3.shape}")
print(f"Размерность массива: {arr3.ndim}")
print(f"Тип данных: {arr3.dtype}")
print(f"Размер элемента в байтах: {arr3.itemsize}")
print(f"Общее количество элементов: {arr3.size}")
# Переформатирование массива
reshaped = arr4.reshape(5, 4) # Изменение формы без изменения данных
flattened = arr3.flatten() # Преобразование в одномерный массив
Форма массива: (2, 3, 4)
Размерность массива: 3
Тип данных: float64
Размер элемента в байтах: 8
Общее количество элементов: 24
Важно отметить, что операции изменения формы, такие как reshape, не создают копию данных, если это возможно, а оперируют с оригинальным массивом через альтернативное представление. Это так называемые «view» операции, которые экономят память и ускоряют работу. Однако некоторые операции, например, flatten(), всегда создают копию данных.
Векторизованные операции в NumPy
Одно из главных преимуществ NumPy – возможность выполнять операции над всем массивом без явных циклов, что называется векторизацией. В своей практике я регулярно сталкиваюсь с задачами, где векторизованные операции ускоряют вычисления в десятки, а иногда и сотни раз по сравнению с эквивалентным кодом на чистом Python.
Рассмотрим простой пример – вычисление евклидова расстояния между двумя наборами точек:
import numpy as np
import time
# Генерируем два набора из 1 миллиона точек в 3D пространстве
points1 = np.random.random((1000000, 3))
points2 = np.random.random((1000000, 3))
# Количество точек для теста
N = 1000000
# Метод с использованием циклов Python
def euclidean_distance_loop(p1, p2):
result = np.empty(len(p1))
for i in range(len(p1)):
squared_diff_sum = 0
for j in range(len(p1[i])):
squared_diff_sum += (p1[i][j] - p2[i][j]) ** 2
result[i] = np.sqrt(squared_diff_sum)
return result
# Векторизованный метод NumPy
def euclidean_distance_vectorized(p1, p2):
return np.sqrt(np.sum((p1 - p2) ** 2, axis=1))
# Берём одинаковое подмножество для честного сравнения
p1_sub = points1[:N]
p2_sub = points2[:N]
# Сравниваем производительность
start = time.time()
dist_loop = euclidean_distance_loop(p1_sub, p2_sub)
print(f"Python loop time: {time.time() - start:.4f} seconds")
start = time.time()
dist_vectorized = euclidean_distance_vectorized(p1_sub, p2_sub)
print(f"NumPy vectorized time: {time.time() - start:.4f} seconds")
# Проверяем точность результатов
print(f"Results match: {np.allclose(dist_loop, dist_vectorized)}")
Python loop time: 4.0288 seconds
NumPy vectorized time: 0.0314 seconds
Results match: True
Результаты такого сравнения обычно показывают разницу в производительности в сотни раз в пользу векторизованного подхода. Причины такого преимущества кроются в следующем:
- Операции выполняются на уровне оптимизированного C-кода, а не интерпретатора Python;
- Используются SIMD-инструкции процессора (Single Instruction Multiple Data);
- Минимизируются накладные расходы на вызовы функций и проверки типов;
- Эффективнее используется кэш процессора благодаря последовательному доступу к памяти.
Важно помнить, что векторизация – это не просто синтаксический сахар, а фундаментально иной подход к организации вычислений, ориентированный на параллельную обработку данных.
Продвинутые техники индексации
Гибкие возможности индексации в NumPy позволяют элегантно решать многие задачи обработки данных без громоздких циклов. В отличие от стандартных списков Python, NumPy поддерживает многомерное индексирование, индексирование булевыми массивами и расширенные срезы.
Приведу несколько примеров продвинутой индексации, которые я часто использую в своей работе:
import numpy as np
# Создаем тестовый массив
arr = np.arange(16).reshape(4, 4)
print("Исходный массив:")
print(arr)
# 1. Многомерное индексирование
row_indices = np.array([0, 2, 3])
col_indices = np.array([1, 2, 0])
# Выбираем элементы (0,1), (2,2), (3,0)
selected = arr[row_indices, col_indices]
print("\nВыбранные элементы по парам индексов:")
print(selected) # [1, 10, 12]
# 2. Булево индексирование
mask = arr > 7
print("\nМаска для элементов > 7:")
print(mask)
print("\nЭлементы, удовлетворяющие условию:")
print(arr[mask])
# 3. Комбинированное индексирование
print("\nСтроки 1-3, значения между 5 и 12:")
complex_selection = arr[1:4, :][arr[1:4, :] > 5][arr[1:4, :][arr[1:4, :] > 5] < 12]
print(complex_selection)
# 4. Изменение определенных элементов
arr_modified = arr.copy()
# Заменим все четные числа на -1
arr_modified[arr_modified % 2 == 0] = -1
print("\nМассив с замененными четными числами:")
print(arr_modified)
# 5. Fancy indexing для переупорядочивания
shuffled_indices = np.random.permutation(4)
print("\nПеремешанные индексы:")
print(shuffled_indices)
print("\nПеремешанные строки:")
print(arr[shuffled_indices])
Исходный массив:
[[ 0 1 2 3]
[ 4 5 6 7]
[ 8 9 10 11]
[12 13 14 15]]
Выбранные элементы по парам индексов:
[ 1 10 12]
Маска для элементов > 7:
[[False False False False]
[False False False False]
[ True True True True]
[ True True True True]]
Элементы, удовлетворяющие условию:
[ 8 9 10 11 12 13 14 15]
Строки 1-3, значения между 5 и 12:
[ 6 7 8 9 10 11]
Массив с замененными четными числами:
[[-1 1 -1 3]
[-1 5 -1 7]
[-1 9 -1 11]
[-1 13 -1 15]]
Перемешанные индексы:
[1 0 3 2]
Перемешанные строки:
[[ 4 5 6 7]
[ 0 1 2 3]
[12 13 14 15]
[ 8 9 10 11]]
Особое внимание стоит обратить на разницу между базовым срезом (arr[1:3]) и расширенным индексированием (arr[[1,2]]). Базовый срез создает представление исходного массива (view), изменения в котором отражаются на исходном массиве. Расширенное индексирование, напротив, всегда создает копию данных.
Умелое использование этих техник позволяет писать лаконичный и эффективный код для манипуляций с данными, что особенно важно при анализе больших массивов информации.
Универсальные функции (ufuncs)
Универсальные функции в NumPy представляют собой операции, которые выполняются поэлементно на массивах. Они реализованы на C и предоставляют высокопроизводительные альтернативы стандартным функциям Python. В контексте дескриптивного анализа ufuncs позволяют быстро вычислять различные статистики и трансформации данных.
Вот несколько примеров использования универсальных функций для анализа данных:
import numpy as np
# Создаем тестовый массив данных, имитирующий некоторые измерения
data = np.random.normal(100, 15, 10000) # 10000 значений с нормальным распределением
# Базовая статистика с использованием ufuncs
print(f"Минимум: {np.min(data):.2f}")
print(f"Максимум: {np.max(data):.2f}")
print(f"Сумма: {np.sum(data):.2f}")
print(f"Среднее: {np.mean(data):.2f}")
print(f"Медиана: {np.median(data):.2f}")
print(f"Стандартное отклонение: {np.std(data):.2f}")
print(f"Дисперсия: {np.var(data):.2f}")
# Квантили и процентили
percentiles = np.percentile(data, [25, 50, 75, 90, 95, 99])
print("\nПроцентили (25%, 50%, 75%, 90%, 95%, 99%):")
for p, val in zip([25, 50, 75, 90, 95, 99], percentiles):
print(f"{p}%: {val:.2f}")
# Подсчет элементов, удовлетворяющих условию
above_120 = np.sum(data > 120)
print(f"\nКоличество значений > 120: {above_120} ({above_120/len(data)*100:.2f}%)")
# Нормализация данных (Z-score)
z_scores = (data - np.mean(data)) / np.std(data)
print(f"\nZ-scores - среднее: {np.mean(z_scores):.2f}, станд. отклонение: {np.std(z_scores):.2f}")
# Определение выбросов (значения, отстоящие более чем на 3 стандартных отклонения)
outliers = data[np.abs(z_scores) > 3]
print(f"Количество выбросов: {len(outliers)} ({len(outliers)/len(data)*100:.2f}%)")
print(f"Значения выбросов: {outliers[:5]}...") # Показываем первые 5 выбросов
# Логарифмическое преобразование (полезно для данных с правосторонней асимметрией)
log_data = np.log(np.abs(data))
print(f"\nПосле логарифмирования - среднее: {np.mean(log_data):.2f}, станд. отклонение: {np.std(log_data):.2f}")
# Бинаризация данных (пороговая функция)
binary_data = np.where(data > np.mean(data), 1, 0)
print(f"\nПосле бинаризации - количество 1: {np.sum(binary_data)} ({np.mean(binary_data)*100:.2f}%)")
Минимум: 46.21
Максимум: 160.61
Сумма: 999589.62
Среднее: 99.96
Медиана: 100.01
Стандартное отклонение: 14.96
Дисперсия: 223.95
Процентили (25%, 50%, 75%, 90%, 95%, 99%):
25%: 89.80
50%: 100.01
75%: 110.11
90%: 119.00
95%: 124.37
99%: 135.62
Количество значений > 120: 884 (8.84%)
Z-scores — среднее: -0.00, станд. отклонение: 1.00
Количество выбросов: 31 (0.31%)
Значения выбросов: [ 54.40837059 50.32427838 51.65957967 153.39431996 146.70820359]…
После логарифмирования — среднее: 4.59, станд. отклонение: 0.15
После бинаризации — количество 1: 5012 (50.12%)
Важным свойством универсальных функций является возможность объединять их в цепочки операций, которые выполняются без создания промежуточных массивов. Это позволяет оптимизировать использование памяти и повысить производительность кода.
Например, вместо последовательности:
temp1 = np.abs(data)
temp2 = np.log(temp1)
result = np.sqrt(temp2)
можно написать:
result = np.sqrt(np.log(np.abs(data)))
В этом случае промежуточные результаты не сохраняются в отдельных массивах, что экономит память и время.
Также стоит упомянуть о возможности создания собственных универсальных функций с помощью декоратора @np.vectorize или функции np.frompyfunc(). Однако важно понимать, что такие функции не будут столь же оптимизированы, как встроенные ufuncs, и могут работать медленнее, чем хорошо векторизованный код с использованием стандартных функций NumPy.
Pandas: анализ структурированных данных
Если NumPy представляет собой фундамент для численных вычислений, то Pandas – это мощная надстройка, специализированная на работе со структурированными данными.
В своей практике я обнаружил, что Pandas особенно удобен для анализа табличных данных, временных рядов и работы с разнородной информацией. Библиотека предоставляет интуитивно понятный интерфейс для манипуляций с данными и включает множество встроенных функций для дескриптивного анализа.
Манипуляции с Dataframes
Основным объектом Pandas является Dataframe – двумерная таблица с метками строк и столбцов. По сути, Dataframe можно представить как словарь Series (одномерных массивов), где каждый Series соответствует отдельному столбцу таблицы. Такая структура идеально подходит для представления гетерогенных данных, с которыми мы часто сталкиваемся при анализе временных рядов и структурированных данных.
Рассмотрим основные способы создания и манипуляции с DataFrame:
import pandas as pd
import numpy as np
# Создание DataFrame из словаря
data = {
'имя': ['Анна', 'Борис', 'Вера', 'Григорий', 'Дарья'],
'возраст': [28, 34, 29, 42, 31],
'отдел': ['Маркетинг', 'IT', 'Финансы', 'IT', 'Маркетинг'],
'стаж': [2.5, 7, 4.2, 15, 3.7],
'премия': [True, False, True, True, False]
}
df = pd.DataFrame(data)
print("Исходный DataFrame:")
print(df)
# Базовая информация о DataFrame
print("\nРазмерность DataFrame:", df.shape)
print("\nТипы данных в столбцах:")
print(df.dtypes)
print("\nСтатистическая информация:")
print(df.describe())
# Доступ к данным
print("\nПервый столбец (имя):")
print(df['имя'])
print("\nПервая строка:")
print(df.iloc[0])
print("\nСотрудники отдела IT:")
print(df[df['отдел'] == 'IT'])
# Добавление нового столбца
df['зарплата'] = [65000, 120000, 90000, 150000, 70000]
print("\nDataFrame с новым столбцом:")
print(df)
# Вычисляемый столбец
df['налог'] = df['зарплата'] * 0.13
print("\nDataFrame с вычисляемым столбцом:")
print(df)
# Группировка и агрегация
print("\nСредняя зарплата по отделам:")
print(df.groupby('отдел')['зарплата'].mean())
# Сортировка
print("\nDataFrame, отсортированный по зарплате (по убыванию):")
print(df.sort_values('зарплата', ascending=False))
Исходный DataFrame:
имя возраст отдел стаж премия
0 Анна 28 Маркетинг 2.5 True
1 Борис 34 IT 7.0 False
2 Вера 29 Финансы 4.2 True
3 Григорий 42 IT 15.0 True
4 Дарья 31 Маркетинг 3.7 False
Размерность DataFrame: (5, 5)
Типы данных в столбцах:
имя object
возраст int64
отдел object
стаж float64
премия bool
dtype: object
Статистическая информация:
возраст стаж
count 5.000000 5.000000
mean 32.800000 6.480000
std 5.630275 5.040536
min 28.000000 2.500000
25% 29.000000 3.700000
50% 31.000000 4.200000
75% 34.000000 7.000000
max 42.000000 15.000000
Первый столбец (имя):
0 Анна
1 Борис
2 Вера
3 Григорий
4 Дарья
Name: имя, dtype: object
Первая строка:
имя Анна
возраст 28
отдел Маркетинг
стаж 2.5
премия True
Name: 0, dtype: object
Сотрудники отдела IT:
имя возраст отдел стаж премия
1 Борис 34 IT 7.0 False
3 Григорий 42 IT 15.0 True
DataFrame с новым столбцом:
имя возраст отдел стаж премия зарплата
0 Анна 28 Маркетинг 2.5 True 65000
1 Борис 34 IT 7.0 False 120000
2 Вера 29 Финансы 4.2 True 90000
3 Григорий 42 IT 15.0 True 150000
4 Дарья 31 Маркетинг 3.7 False 70000
DataFrame с вычисляемым столбцом:
имя возраст отдел стаж премия зарплата налог
0 Анна 28 Маркетинг 2.5 True 65000 8450.0
1 Борис 34 IT 7.0 False 120000 15600.0
2 Вера 29 Финансы 4.2 True 90000 11700.0
3 Григорий 42 IT 15.0 True 150000 19500.0
4 Дарья 31 Маркетинг 3.7 False 70000 9100.0
Средняя зарплата по отделам:
отдел
IT 135000.0
Маркетинг 67500.0
Финансы 90000.0
Name: зарплата, dtype: float64
DataFrame, отсортированный по зарплате (по убыванию):
имя возраст отдел стаж премия зарплата налог
3 Григорий 42 IT 15.0 True 150000 19500.0
1 Борис 34 IT 7.0 False 120000 15600.0
2 Вера 29 Финансы 4.2 True 90000 11700.0
4 Дарья 31 Маркетинг 3.7 False 70000 9100.0
0 Анна 28 Маркетинг 2.5 True 65000 8450.0
Важно отметить несколько особенностей Pandas-овских датафреймов, которые делают его особенно удобным для анализа данных:
- Автоматическое выравнивание индексов – при операциях с несколькими DataFrame данные автоматически выравниваются по индексам, что минимизирует ошибки при объединении неполных данных;
- Встроенная обработка пропущенных значений – Pandas предлагает множество методов для обнаружения, заполнения или удаления NA/NaN значений;
- Интеллектуальное преобразование типов – при загрузке данных Pandas пытается автоматически определить оптимальные типы данных для столбцов;
- Встроенная поддержка иерархических индексов – позволяет работать с многомерными данными в плоском представлении.
Благодаря этим возможностям, датафрейм Pandas стал de-facto стандартом для представления табличных данных в экосистеме Python, и я рекомендую его использование для большинства задач дескриптивного анализа.
Оптимизация загрузки и предобработки данных
Прежде чем приступить к дескриптивному анализу, необходимо загрузить и правильно подготовить данные. Pandas предоставляет множество функций для чтения данных из различных источников, включая CSV, Excel, SQL, JSON и многие другие форматы.
В следующем примере я покажу полный процесс загрузки, очистки и подготовки данных к анализу:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime
# Предположим, у нас есть CSV файл с данными о продажах
# Создадим сначала такой файл для демонстрации
sales_data = pd.DataFrame({
'дата': pd.date_range(start='2023-01-01', periods=100),
'товар': np.random.choice(['Телефон', 'Ноутбук', 'Планшет', 'Наушники'], 100),
'регион': np.random.choice(['Москва', 'Санкт-Петербург', 'Новосибирск', 'Екатеринбург'], 100),
'продавец': np.random.choice(['Иванова', 'Петров', 'Сидоров', 'Кузнецова'], 100),
'количество': np.random.randint(1, 10, 100),
'цена': [round(np.random.uniform(5000, 150000), -3) for _ in range(100)]
})
# Внесем немного "шума" в данные - пропущенные значения, дубликаты, ошибки
sales_data.loc[np.random.choice(100, 5), 'количество'] = np.nan # Пропущенные значения
sales_data.loc[np.random.choice(100, 3), 'продавец'] = None # Еще пропущенные значения
sales_data.loc[np.random.choice(100, 2), 'цена'] = -1000 # Ошибочные значения
# Добавим несколько дубликатов
duplicates = sales_data.sample(5).copy()
sales_data = pd.concat([sales_data, duplicates], ignore_index=True)
# Сохраним в CSV для демонстрации
csv_path = 'sales_data.csv'
sales_data.to_csv(csv_path, index=False)
# Теперь загрузим данные и проведем предварительную обработку
df = pd.read_csv(csv_path)
# 1. Исследуем базовую структуру данных
print(f"Размер DataFrame: {df.shape}")
print("\nПервые 5 строк:")
print(df.head())
print("\nИнформация о DataFrame:")
print(df.info())
print("\nСтатистика по числовым столбцам:")
print(df.describe())
# 2. Проверяем и обрабатываем пропущенные значения
print("\nКоличество пропущенных значений по столбцам:")
print(df.isnull().sum())
# Заполняем пропущенные значения
df['количество'].fillna(df['количество'].median(), inplace=True)
df['продавец'].fillna('Неизвестно', inplace=True)
# 3. Обрабатываем ошибочные значения
print("\nПроверка на отрицательные цены:")
print(df[df['цена'] < 0])
# Исправляем отрицательные цены (заменяем на медианное значение для соответствующего товара)
neg_price_indices = df[df['цена'] < 0].index for idx in neg_price_indices: product = df.loc[idx, 'товар'] median_price = df[df['товар'] == product]['цена'].median() df.loc[idx, 'цена'] = median_price if median_price > 0 else df['цена'].median()
# 4. Удаляем дубликаты
duplicate_count = df.duplicated().sum()
print(f"\nКоличество дубликатов: {duplicate_count}")
if duplicate_count > 0:
df.drop_duplicates(inplace=True)
print(f"Размер после удаления дубликатов: {df.shape}")
# 5. Преобразуем типы данных
# Преобразуем столбец даты в datetime
df['дата'] = pd.to_datetime(df['дата'])
# 6. Создаем дополнительные признаки для анализа
df['месяц'] = df['дата'].dt.month
df['день_недели'] = df['дата'].dt.day_name()
df['выручка'] = df['количество'] * df['цена']
print("\nDataFrame после предварительной обработки:")
print(df.head())
print("\nОбновленная информация:")
print(df.info())
# Проверяем результаты очистки
print("\nПроверка на отрицательные цены после обработки:")
print(df[df['цена'] < 0])
print("\nПроверка на пропущенные значения после обработки:")
print(df.isnull().sum())
Размер DataFrame: (105, 6)
Первые 5 строк:
дата товар регион продавец количество цена
0 2023-01-01 Наушники Новосибирск Петров 3.0 144000.0
1 2023-01-02 Ноутбук Москва Иванова 8.0 68000.0
2 2023-01-03 Наушники Москва Иванова 6.0 125000.0
3 2023-01-04 Планшет Екатеринбург Кузнецова 7.0 49000.0
4 2023-01-05 Телефон Екатеринбург Кузнецова 8.0 26000.0
Информация о DataFrame:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 105 entries, 0 to 104
Data columns (total 6 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 дата 105 non-null object
1 товар 105 non-null object
2 регион 105 non-null object
3 продавец 102 non-null object
4 количество 99 non-null float64
5 цена 105 non-null float64
dtypes: float64(2), object(4)
memory usage: 5.1+ KB
None
Статистика по числовым столбцам:
количество цена
count 99.000000 105.000000
mean 5.434343 73552.380952
std 2.699817 46919.936093
min 1.000000 -1000.000000
25% 3.000000 30000.000000
50% 6.000000 72000.000000
75% 8.000000 118000.000000
max 9.000000 149000.000000
Количество пропущенных значений по столбцам:
дата 0
товар 0
регион 0
продавец 3
количество 6
цена 0
dtype: int64
Проверка на отрицательные цены:
дата товар регион продавец количество цена
64 2023-03-06 Наушники Новосибирск Сидоров 2.0 -1000.0
87 2023-03-29 Ноутбук Москва Петров 7.0 -1000.0
Количество дубликатов: 5
Размер после удаления дубликатов: (100, 6)
DataFrame после предварительной обработки:
дата товар регион продавец количество цена месяц \
0 2023-01-01 Наушники Новосибирск Петров 3.0 144000.0 1
1 2023-01-02 Ноутбук Москва Иванова 8.0 68000.0 1
2 2023-01-03 Наушники Москва Иванова 6.0 125000.0 1
3 2023-01-04 Планшет Екатеринбург Кузнецова 7.0 49000.0 1
4 2023-01-05 Телефон Екатеринбург Кузнецова 8.0 26000.0 1
день_недели выручка
0 Sunday 432000.0
1 Monday 544000.0
2 Tuesday 750000.0
3 Wednesday 343000.0
4 Thursday 208000.0
Обновленная информация:
<class 'pandas.core.frame.DataFrame'>
Index: 100 entries, 0 to 99
Data columns (total 9 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 дата 100 non-null datetime64[ns]
1 товар 100 non-null object
2 регион 100 non-null object
3 продавец 100 non-null object
4 количество 100 non-null float64
5 цена 100 non-null float64
6 месяц 100 non-null int32
7 день_недели 100 non-null object
8 выручка 100 non-null float64
dtypes: datetime64[ns](1), float64(3), int32(1), object(4)
memory usage: 7.4+ KB
None
Проверка на отрицательные цены после обработки:
Empty DataFrame
Columns: [дата, товар, регион, продавец, количество, цена, месяц, день_недели, выручка]
Index: []
Проверка на пропущенные значения после обработки:
дата 0
товар 0
регион 0
продавец 0
количество 0
цена 0
месяц 0
день_недели 0
выручка 0
dtype: int64
Этот пример демонстрирует типичный workflow предварительной обработки данных, включающий:
- Загрузку данных из файла;
- Изучение структуры и базовых статистик;
- Обработку пропущенных значений;
- Исправление ошибочных данных;
- Удаление дубликатов;
- Преобразование типов данных;
- Создание дополнительных признаков для анализа.
В реальных проектах этап предварительной обработки может занимать до 80% времени всего процесса анализа данных, но инвестиции в качественную подготовку данных окупаются повышением точности и надежности последующего анализа.
Группировка и агрегация данных
Группировка и агрегация – это мощные инструменты для выявления паттернов в данных. Pandas предоставляет гибкий интерфейс для этих операций через метод groupby(), который позволяет разбивать данные на группы и применять к ним различные функции.
Рассмотрим различные типы группировок и агрегаций на примере данных о продажах:
import pandas as pd
import numpy as np
# Используем DataFrame из предыдущего примера
# Если его нет, создадим новый набор данных
try:
df
except NameError:
# Создаем данные о продажах
np.random.seed(42) # Для воспроизводимости результатов
df = pd.DataFrame({
'дата': pd.date_range(start='2023-01-01', periods=1000),
'товар': np.random.choice(['Телефон', 'Ноутбук', 'Планшет', 'Наушники'], 1000),
'регион': np.random.choice(['Москва', 'Санкт-Петербург', 'Новосибирск', 'Екатеринбург'], 1000),
'продавец': np.random.choice(['Иванова', 'Петров', 'Сидоров', 'Кузнецова'], 1000),
'количество': np.random.randint(1, 10, 1000),
'цена': [round(np.random.uniform(5000, 150000), -3) for _ in range(1000)]
})
df['месяц'] = df['дата'].dt.month
df['день_недели'] = df['дата'].dt.day_name()
df['выручка'] = df['количество'] * df['цена']
# 1. Базовая группировка по одному столбцу
print("Средняя выручка по товарам:")
print(df.groupby('товар')['выручка'].mean())
# 2. Группировка по нескольким столбцам
print("\nСредняя выручка по товарам и регионам:")
print(df.groupby(['товар', 'регион'])['выручка'].mean())
# 3. Несколько агрегирующих функций
print("\nРазличные статистики по товарам:")
print(df.groupby('товар')['выручка'].agg(['count', 'min', 'max', 'mean', 'std']))
# 4. Разные функции для разных столбцов
agg_funcs = {
'количество': ['sum', 'mean'],
'цена': ['min', 'max', 'mean'],
'выручка': ['sum', 'mean', 'std']
}
print("\nКомплексная агрегация по товарам:")
print(df.groupby('товар').agg(agg_funcs))
# 5. Пользовательские агрегирующие функции
def revenue_to_quantity_ratio(x):
"""Отношение выручки к количеству проданных товаров"""
return x['выручка'].sum() / x['количество'].sum()
def top_sale(x):
"""Максимальная единичная продажа"""
return (x['количество'] * x['цена']).max()
print("\nПользовательские агрегации:")
print(df.groupby('товар').apply(revenue_to_quantity_ratio))
print("\nМаксимальная единичная продажа по продавцам:")
print(df.groupby('продавец').apply(top_sale))
# 6. Группировка с перечислением (transform)
# Добавим столбец с информацией о средней цене товара в данном регионе
df['средняя_цена_в_регионе'] = df.groupby('регион')['цена'].transform('mean')
print("\nДобавлен столбец со средней ценой в регионе:")
print(df[['регион', 'товар', 'цена', 'средняя_цена_в_регионе']].head(10))
# 7. Фильтрация групп
# Отберем только те товары, средняя выручка по которым превышает 300,000
high_revenue_products = df.groupby('товар').filter(lambda x: x['выручка'].mean() > 300000)
print(f"\nТовары с высокой средней выручкой (>300,000): {high_revenue_products['товар'].unique()}")
# 8. Группировка по временным интервалам
print("\nСредняя выручка по месяцам:")
print(df.groupby('месяц')['выручка'].mean())
print("\nСредняя выручка по дням недели:")
print(df.groupby('день_недели')['выручка'].mean())
Средняя выручка по товарам:
товар
Наушники 396782.608696
Ноутбук 408035.714286
Планшет 398280.000000
Телефон 473375.000000
Name: выручка, dtype: float64
Средняя выручка по товарам и регионам:
товар регион
Наушники Екатеринбург 359500.000000
Москва 711500.000000
Новосибирск 150000.000000
Санкт-Петербург 588500.000000
Ноутбук Екатеринбург 531500.000000
Москва 268300.000000
Новосибирск 487142.857143
Санкт-Петербург 428600.000000
Планшет Екатеринбург 186800.000000
Москва 738714.285714
Новосибирск 407285.714286
Санкт-Петербург 166833.333333
Телефон Екатеринбург 611636.363636
Москва 435125.000000
Новосибирск 288000.000000
Санкт-Петербург 192000.000000
Name: выручка, dtype: float64
Различные статистики по товарам:
count min max mean std
товар
Наушники 23 36000.0 1043000.0 396782.608696 324270.784225
Ноутбук 28 24000.0 1128000.0 408035.714286 343109.256127
Планшет 25 23000.0 1251000.0 398280.000000 423709.857489
Телефон 24 18000.0 1260000.0 473375.000000 345670.213844
Комплексная агрегация по товарам:
количество цена выручка \
sum mean min max mean sum
товар
Наушники 116.0 5.043478 6000.0 149000.0 82630.434783 9126000.0
Ноутбук 155.0 5.535714 8000.0 141000.0 72678.571429 11425000.0
Планшет 131.0 5.240000 8000.0 141000.0 65160.000000 9957000.0
Телефон 137.0 5.708333 6000.0 147000.0 84458.333333 11361000.0
mean std
товар
Наушники 396782.608696 324270.784225
Ноутбук 408035.714286 343109.256127
Планшет 398280.000000 423709.857489
Телефон 473375.000000 345670.213844
Пользовательские агрегации:
товар
Наушники 78672.413793
Ноутбук 73709.677419
Планшет 76007.633588
Телефон 82927.007299
dtype: float64
Максимальная единичная продажа по продавцам:
продавец
Иванова 1197000.0
Кузнецова 1008000.0
Неизвестно 405000.0
Петров 1251000.0
Сидоров 1260000.0
dtype: float64
Добавлен столбец со средней ценой в регионе:
регион товар цена средняя_цена_в_регионе
0 Новосибирск Наушники 144000.0 68152.173913
1 Москва Ноутбук 68000.0 78931.034483
2 Москва Наушники 125000.0 78931.034483
3 Екатеринбург Планшет 49000.0 84066.666667
4 Екатеринбург Телефон 26000.0 84066.666667
5 Санкт-Петербург Телефон 50000.0 67388.888889
6 Москва Ноутбук 71000.0 78931.034483
7 Новосибирск Наушники 8000.0 68152.173913
8 Москва Телефон 26000.0 78931.034483
9 Екатеринбург Телефон 37000.0 84066.666667
Товары с высокой средней выручкой (>300,000): ['Наушники' 'Ноутбук' 'Планшет' 'Телефон']
Средняя выручка по месяцам:
месяц
1 403935.483871
2 382428.571429
3 439612.903226
4 501100.000000
Name: выручка, dtype: float64
Средняя выручка по дням недели:
день_недели
Friday 445285.714286
Monday 513200.000000
Saturday 330142.857143
Sunday 324800.000000
Thursday 406071.428571
Tuesday 519642.857143
Wednesday 391642.857143
Name: выручка, dtype: float64
Группировка и агрегация позволяют сжать большие объемы данных до нескольких информативных показателей, что значительно упрощает понимание общих тенденций и закономерностей. В своей практике я часто комбинирую группировку с визуализацией для наглядного представления результатов анализа.
Работа с временными рядами
Pandas имеет мощную поддержку для работы с временными рядами, что делает его незаменимым инструментом для анализа данных с временной составляющей. Я часто использую эти возможности при анализе сезонных трендов, финансовых данных и других временных последовательностей.
Рассмотрим основные методы анализа временных рядов:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# Создаем временной ряд для демонстрации
# Будем моделировать ежедневные продажи с трендом, сезонностью и случайным шумом
np.random.seed(42)
# Генерируем временной индекс
date_range = pd.date_range('2023-01-01', '2023-12-31', freq='D')
# Базовый тренд - линейный рост
trend = np.linspace(100, 200, len(date_range))
# Сезонность - годовая и недельная
annual_seasonality = 50 * np.sin(2 * np.pi * np.arange(len(date_range)) / 365)
weekly_seasonality = 30 * (np.arange(len(date_range)) % 7 < 2) # Выходные дни
# Добавляем случайный шум
noise = np.random.normal(0, 5, len(date_range))
# Объединяем компоненты
sales = trend + annual_seasonality + weekly_seasonality + noise
# Создаем Series с временным индексом
sales_ts = pd.Series(sales, index=date_range, name='продажи')
# Визуализируем исходный временной ряд
plt.figure(figsize=(12, 6))
sales_ts.plot()
plt.title('Ежедневные продажи за 2023 гг.')
plt.ylabel('Объем продаж')
plt.grid(True)
plt.show()
# 1. Ресемплинг - изменение частоты временного ряда
# Месячный ресемплинг
monthly_sales = sales_ts.resample('M').mean()
print("Средние месячные продажи:")
print(monthly_sales.head())
# Недельный ресемплинг
weekly_sales = sales_ts.resample('W').sum()
print("\nНедельные суммы продаж:")
print(weekly_sales.head())
# 2. Скользящие средние для сглаживания
# 7-дневное скользящее среднее
sales_ma7 = sales_ts.rolling(window=7).mean()
# 30-дневное скользящее среднее
sales_ma30 = sales_ts.rolling(window=30).mean()
plt.figure(figsize=(12, 6))
sales_ts.plot(alpha=0.5, label='Исходные данные')
sales_ma7.plot(label='7-дневное скользящее среднее')
sales_ma30.plot(label='30-дневное скользящее среднее')
plt.title('Сглаживание временного ряда скользящими средними')
plt.legend()
plt.grid(True)
plt.show()
# 3. Сдвиг данных (lag) для анализа зависимостей
# Вычисляем изменение относительно предыдущего дня
sales_ts_diff = sales_ts.diff()
# Вычисляем изменение относительно предыдущей недели
sales_ts_diff_7 = sales_ts.diff(7)
plt.figure(figsize=(12, 8))
plt.subplot(2, 1, 1)
sales_ts_diff.plot()
plt.title('Изменение продаж относительно предыдущего дня')
plt.grid(True)
plt.subplot(2, 1, 2)
sales_ts_diff_7.plot()
plt.title('Изменение продаж относительно предыдущей недели')
plt.grid(True)
plt.tight_layout()
plt.show()
# 4. Анализ сезонности по дням недели
sales_by_weekday = sales_ts.groupby(sales_ts.index.dayofweek).mean()
days = ['Понедельник', 'Вторник', 'Среда', 'Четверг', 'Пятница', 'Суббота', 'Воскресенье']
sales_by_weekday.index = days
plt.figure(figsize=(10, 5))
sales_by_weekday.plot(kind='bar')
plt.title('Средние продажи по дням недели')
plt.ylabel('Средний объем продаж')
plt.grid(True, axis='y')
plt.show()
# 5. Анализ сезонности по месяцам
sales_by_month = sales_ts.groupby(sales_ts.index.month).mean()
months = ['Янв', 'Фев', 'Мар', 'Апр', 'Май', 'Июн', 'Июл', 'Авг', 'Сен', 'Окт', 'Ноя', 'Дек']
sales_by_month.index = months
plt.figure(figsize=(10, 5))
sales_by_month.plot(kind='bar')
plt.title('Средние продажи по месяцам')
plt.ylabel('Средний объем продаж')
plt.grid(True, axis='y')
plt.show()
# 6. Автокорреляция для выявления периодичности
from pandas.plotting import autocorrelation_plot
plt.figure(figsize=(12, 6))
autocorrelation_plot(sales_ts)
plt.title('Автокорреляционная функция продаж')
plt.grid(True)
plt.show()
Рис. 1: График ежедневных продаж за 2023 г
Рис. 2: Сглаживание временного ряда скользящими средними
Рис. 3: Графики изменения продаж относительно предыдущего дня / предыдущей недели
Средние месячные продажи:
2023-01-31 125.407725
2023-02-28 154.410769
2023-03-31 175.538328
2023-04-30 185.773726
2023-05-31 181.425549
Freq: ME, Name: продажи, dtype: float64
Недельные суммы продаж:
2023-01-01 132.483571
2023-01-08 811.312944
2023-01-15 825.732669
2023-01-22 889.567941
2023-01-29 938.175089
Freq: W-SUN, Name: продажи, dtype: float64
Рис. 4: Средний объем продаж по дням недели
Рис. 5: Средний объем продаж по месяцам
Рис. 6: Автокорреляционная функция динамики объема продаж
Анализ временных рядов особенно важен для бизнес-аналитики, где понимание сезонных паттернов и долгосрочных трендов может дать значительное конкурентное преимущество. Pandas с его удобным API для работы с датами и временем существенно упрощает эту задачу.
Разбиение данных на категории с помощью pd.cut и квантилей
В процессе дескриптивного анализа часто возникает необходимость разбить непрерывные данные на категории или группы. Pandas предоставляет для этого несколько удобных функций, в частности, pd.cut и pd.qcut. Их отличия в следующем:
- pd.cut разбивает данные на категории по заданным интервалам;
- pd.qcut использует квантили для создания групп с примерно равным количеством наблюдений.
Оба метода чрезвычайно полезны для бинирования данных и последующего анализа.
Рассмотрим практические примеры использования этих функций:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# Создаем датасет для демонстрации
np.random.seed(42)
data = {
'возраст': np.random.normal(35, 10, 1000).clip(18, 70), # Возраст от 18 до 70
'доход': np.random.lognormal(10, 0.6, 1000), # Логнормальное распределение для дохода
'стаж': np.random.exponential(10, 1000).clip(0, 40), # Стаж работы от 0 до 40 лет
'расходы': np.random.gamma(shape=5, scale=20000, size=1000) # Гамма-распределение для расходов
}
df = pd.DataFrame(data)
# 1. Разбиение на фиксированные интервалы с помощью pd.cut
# Разбиваем возраст на 5 равных интервалов
df['возрастная_группа'] = pd.cut(df['возраст'], bins=5)
print("Разбиение возраста на 5 равных интервалов:")
print(df['возрастная_группа'].value_counts().sort_index())
# Визуализация распределения
plt.figure(figsize=(10, 6))
sns.countplot(x='возрастная_группа', data=df)
plt.title('Распределение по возрастным группам (равные интервалы)')
plt.xlabel('Возрастная группа')
plt.ylabel('Количество')
plt.xticks(rotation=45)
plt.grid(True, axis='y')
plt.tight_layout()
plt.show()
# 2. Разбиение с пользовательскими интервалами
custom_bins = [18, 25, 35, 50, 70]
custom_labels = ['18-25', '26-35', '36-50', '51-70']
df['возрастная_категория'] = pd.cut(df['возраст'], bins=custom_bins, labels=custom_labels)
print("\nРазбиение возраста на пользовательские интервалы:")
print(df['возрастная_категория'].value_counts().sort_index())
# Визуализация
plt.figure(figsize=(10, 6))
sns.countplot(x='возрастная_категория', data=df)
plt.title('Распределение по возрастным категориям (пользовательские интервалы)')
plt.xlabel('Возрастная категория')
plt.ylabel('Количество')
plt.grid(True, axis='y')
plt.tight_layout()
plt.show()
# 3. Разбиение на равные группы с помощью pd.qcut (квантили)
# Разбиваем доход на квартили (4 равные группы)
df['доход_квартиль'] = pd.qcut(df['доход'], q=4)
print("\nРазбиение дохода на квартили:")
print(df['доход_квартиль'].value_counts().sort_index())
# Разбиваем доход на децили (10 равных групп)
df['доход_дециль'] = pd.qcut(df['доход'], q=10, labels=False) # labels=False возвращает номера групп (0-9)
print("\nРазбиение дохода на децили (количество наблюдений в каждой группе):")
print(df['доход_дециль'].value_counts().sort_index())
# Визуализация средних расходов по группам дохода
income_expense = df.groupby('доход_дециль')['расходы'].mean().reset_index()
plt.figure(figsize=(12, 6))
sns.barplot(x='доход_дециль', y='расходы', data=income_expense)
plt.title('Средние расходы по децилям дохода')
plt.xlabel('Дециль дохода (0 - низший, 9 - высший)')
plt.ylabel('Средние расходы')
plt.grid(True, axis='y')
plt.tight_layout()
plt.show()
# 4. Комбинирование qcut и группировки для сложного анализа
# Разбиваем на группы по возрасту и стажу и анализируем средний доход
df['возраст_группа'] = pd.qcut(df['возраст'], q=3, labels=['молодой', 'средний', 'старший'])
df['стаж_группа'] = pd.qcut(df['стаж'], q=3, labels=['начинающий', 'опытный', 'эксперт'])
# Создаем сводную таблицу средних доходов по группам
income_by_age_experience = df.pivot_table(
values='доход',
index='возраст_группа',
columns='стаж_группа',
aggfunc='mean'
)
print("\nСредний доход по группам возраста и стажа:")
print(income_by_age_experience)
# Визуализация в виде тепловой карты
plt.figure(figsize=(10, 8))
sns.heatmap(income_by_age_experience, annot=True, fmt='.0f', cmap='YlGnBu')
plt.title('Средний доход по группам возраста и стажа')
plt.tight_layout()
plt.show()
# 5. Использование cut и qcut для детальной визуализации распределения
plt.figure(figsize=(12, 10))
# Распределение дохода с цветовым кодированием по возрастным группам
plt.subplot(2, 1, 1)
for category in df['возрастная_категория'].unique():
subset = df[df['возрастная_категория'] == category]
sns.kdeplot(subset['доход'], label=category)
plt.title('Распределение дохода по возрастным категориям')
plt.xlabel('Доход')
plt.ylabel('Плотность')
plt.legend()
plt.grid(True)
# Распределение стажа с цветовым кодированием по квартилям дохода
plt.subplot(2, 1, 2)
for quartile in sorted(df['доход_квартиль'].unique()):
subset = df[df['доход_квартиль'] == quartile]
sns.kdeplot(subset['стаж'], label=str(quartile))
plt.title('Распределение стажа по квартилям дохода')
plt.xlabel('Стаж (лет)')
plt.ylabel('Плотность')
plt.legend()
plt.grid(True)
plt.tight_layout()
plt.show()
Разбиение возраста на 5 равных интервалов:
возрастная_группа
(17.948, 28.4] 245
(28.4, 38.8] 412
(38.8, 49.2] 254
(49.2, 59.6] 81
(59.6, 70.0] 8
Name: count, dtype: int64
Рис. 7: Распределение данных по равным интервалам по возрастным группам
Разбиение возраста на пользовательские интервалы:
возрастная_категория
18-25 112
26-35 343
36-50 435
51-70 75
Name: count, dtype: int64
Рис. 8: Разбиение данных на кастомные интервалы по возрастным категориям
Разбиение дохода на квартили:
доход_квартиль
(3773.531, 15309.903] 250
(15309.903, 22876.069] 250
(22876.069, 34109.464] 250
(34109.464, 149621.573] 250
Name: count, dtype: int64
Разбиение дохода на децили (количество наблюдений в каждой группе):
доход_дециль
0 100
1 100
2 100
3 100
4 100
5 100
6 100
7 100
8 100
9 100
Name: count, dtype: int64
Рис. 9: Разбиение данных на децили
Средний доход по группам возраста и стажа:
стаж_группа начинающий опытный эксперт
возраст_группа
молодой 27004.098964 29092.116224 29798.127605
средний 24062.017262 26617.586429 27431.922075
старший 28273.014568 27177.327723 27359.578718
Рис. 10: Тепловая карта совпадений среднего дохода по группам возраста и стажа
Рис. 11: Распределение дохода по возрастным категориям и распределение стажа по квартилям дохода
Использование pd.cut и pd.qcut открывает широкие возможности для категоризации данных и последующего анализа взаимосвязей между различными переменными. Особенно полезны эти методы при подготовке данных для визуализации и создания понятных бизнес-отчетов, где важно представить информацию в агрегированной форме.
Анализ взаимосвязей между переменными
После изучения распределений отдельных переменных следующим логическим шагом является анализ их взаимосвязей. Pandas и NumPy предоставляют различные инструменты для такого анализа, от простых корреляций до более сложных методов.
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from scipy import stats
# Используем данные из предыдущего примера или создаем новый датасет
try:
df
except NameError:
np.random.seed(42)
df = pd.DataFrame({
'возраст': np.random.normal(35, 10, 1000).clip(18, 70),
'доход': np.random.lognormal(10, 0.6, 1000),
'стаж': np.random.exponential(10, 1000).clip(0, 40),
'расходы': np.random.gamma(shape=5, scale=20000, size=1000),
'образование': np.random.choice(
['Среднее', 'Бакалавр', 'Магистр', 'Кандидат наук', 'Доктор наук'],
size=1000,
p=[0.2, 0.4, 0.3, 0.08, 0.02]
)
})
# 1. Диаграммы рассеяния (Scatter plots)
# Создаем матрицу диаграмм рассеяния
plt.figure(figsize=(16, 12))
sns.pairplot(df.select_dtypes(include=[np.number]), diag_kind='kde')
plt.suptitle('Матрица диаграмм рассеяния', y=1.02, fontsize=16)
plt.show()
# 2. Выборочная диаграмма рассеяния с линией регрессии
plt.figure(figsize=(10, 6))
sns.regplot(x='стаж', y='доход', data=df, scatter_kws={'alpha':0.5})
plt.title('Зависимость дохода от стажа с линией регрессии')
plt.grid(True)
plt.show()
# 3. Корреляционный анализ
# Коэффициент корреляции Пирсона (линейная зависимость)
pearson_corr = df.select_dtypes(include=[np.number]).corr(method='pearson')
print("Корреляция Пирсона:")
print(pearson_corr)
# Коэффициент ранговой корреляции Спирмена (монотонная зависимость)
spearman_corr = df.select_dtypes(include=[np.number]).corr(method='spearman')
print("\nКорреляция Спирмена:")
print(spearman_corr)
# Визуализация различий между корреляциями Пирсона и Спирмена
fig, axes = plt.subplots(1, 2, figsize=(16, 7))
sns.heatmap(pearson_corr, annot=True, cmap='coolwarm', vmin=-1, vmax=1, ax=axes[0])
axes[0].set_title('Корреляция Пирсона')
sns.heatmap(spearman_corr, annot=True, cmap='coolwarm', vmin=-1, vmax=1, ax=axes[1])
axes[1].set_title('Корреляция Спирмена')
plt.tight_layout()
plt.show()
Рис. 12: Матрица диаграмм рассеяния
Рис. 13: Зависимость дохода от стажа с линией регрессии
Корреляция Пирсона:
возраст доход стаж расходы доход_дециль
возраст 1.000000 -0.027888 0.048552 0.040649 -0.060214
доход -0.027888 1.000000 0.008092 -0.020345 0.863737
стаж 0.048552 0.008092 1.000000 0.014937 0.020211
расходы 0.040649 -0.020345 0.014937 1.000000 -0.008137
доход_дециль -0.060214 0.863737 0.020211 -0.008137 1.000000
Корреляция Спирмена:
возраст доход стаж расходы доход_дециль
возраст 1.000000 -0.063610 0.047525 0.035456 -0.066834
доход -0.063610 1.000000 0.027145 -0.023067 0.994988
стаж 0.047525 0.027145 1.000000 0.035572 0.025987
расходы 0.035456 -0.023067 0.035572 1.000000 -0.020330
доход_дециль -0.066834 0.994988 0.025987 -0.020330 1.000000
Рис. 14: Матрицы корреляций по Пирсону и Спирмену
Заключение
В этой статье мы рассмотрели ключевые возможности библиотек NumPy и Pandas, которые делают Python мощным инструментом для дескриптивного анализа данных.
NumPy предоставляет высокоэффективную реализацию массивов (ndarray) и оптимизированных функций для работы с ними. Он особенно полезен, когда требуется высокая производительность и точный контроль над памятью.
Pandas же расширяет эти возможности, предоставляя удобные структуры данных (Series, DataFrame) и богатый набор методов для анализа, агрегации и визуализации табличных данных. Особенно ценны его средства работы с пропущенными данными, временными рядами и категориальными признаками.
Мы рассмотрели такие важные техники, как:
- Базовая статистика (медиана, мода, асимметрия, эксцесс);
- Корреляционный анализ;
- Группировка и агрегация данных;
- Работа с временными рядами и сезонностью;
- Разбиение данных на интервалы и квантили;
- Визуализация зависимостей между переменными.
Особое внимание было уделено взаимосвязям между переменными, поскольку именно они позволяют выявлять скрытые закономерности и строить прогнозные модели. Мы использовали диаграммы рассеяния, коэффициенты корреляции Пирсона и Спирмена, а также визуализировали результаты с помощью тепловых карт и графиков.
На практике комбинация NumPy и Pandas становится основой для любого серьезного анализа данных. Эти библиотеки обеспечивают не только гибкость и скорость обработки, но и глубокую интеграцию с другими популярными инструментами экосистемы Python, такими как Matplotlib, Seaborn, Scikit-learn и Statsmodels.