import pandas as pd
from pathlib import Path
import seaborn as sns
import matplotlib.pyplot as plt
# Указываем путь к папке, где лежат Excel-файлы
path = 'data/gbif/'
# Преобразуем строку пути в объект Path (из библиотеки pathlib)
path = Path(path)
# Создаем пустой список, куда будем сохранять считанные датафреймы
dfs = []
# Перебираем все файлы с расширением .xlsx в указанной папке
for gbif in path.glob('*.xlsx'):
# Считываем из каждого Excel-файла лист с названием 'GBIF' в датафрейм
df = pd.read_excel(gbif, sheet_name='GBIF')
# Добавляем считанный датафрейм в список dfs
dfs.append(df)
# Объединяем все датафреймы из списка dfs в один общий датафрейм
# pd.concat "склеивает" их по строкам (по умолчанию axis=0)
df = pd.concat(dfs)
Библиотеки os и pathlib мы подробно обсуждаем на курсе по питону на занятии «Биологические объекты и файловые пути»
# Загружает CSV-файл
# Аргумент index_col указывает, какой столбец использовать в качестве индекса
df = pd.read_csv('file.csv', index_col='index_column')
# Загружает TSV-файл (таблица, где значения разделены табуляцией '\t')
# Аргумент sep='\t' задает разделитель столбцов
df = pd.read_csv('file.tsv', sep='\t')
# Загружает JSON-файл
# Pandas автоматически преобразует данные JSON в табличную структуру, если это возможно
df = pd.read_json('file.json')
df — это типичное имя переменной для DataFrame. Датафрейм — основная двумерная структура данных библиотеки Pandas, напоминающая таблицу в Excel или SQL. Rаждый столбец в DataFrame — отдельный объект типа Series.
# По умолчанию показывает первые пять строк датафрейма
df.head()
# По умолчанию показывает последние пять строк датафрейма
df.tail()
# Возвращает одну случайную строку из датафрейма
df.sample()
# Выводит базовую статистику по числовым столбцам:
# количество значений (count), среднее (mean), стандартное отклонение (std),
# минимумы (min), максимумы (max), квартили
df.describe()
# Отображает краткую сводку о датафрейме:
# количество строк и столбцов, названия колонок, типы данных,
# количество ненулевых (ненулевых/ненановых) значений и общий объем памяти
df.info()
# Атрибут, который показывает список всех колонок в датафрейме
df.columns
# Создаем список с названиями нужных колонок
# Многострочная строка ('''...''') тут очень удобна
cols = '''
occurrenceID
catalogNumber
...
eventDate
associatedOccurrences
associatedTaxa
'''.splitlines()
# Убираем возможные лишние пробелы и пустые строки из списка
cols = [c.strip() for c in cols if c]
# Оставляем в датафрейме только указанные колонки
df = df[cols]
search_duplicates = '''scientificName
associatedTaxa
eventDate
decimalLatitude
decimalLongitude
associatedReferences'''.split()
df.shape[0]
# 14091
df = df.drop_duplicates(subset=search_duplicates)
df.shape[0]
# 10244
df.minimumDepthInMeters.dropna()
Другие методы включают заполнение NaN средними/медианами по соответствующим подгруппам (imputation) или использование моделей машинного обучения для предсказания пропущенных значений. В некоторых случаях пропуск может означать «ниже порога определения», и тогда его заменяют нулем df.fillna(0)
df.minimumDepthInMeters = df.minimumDepthInMeters.str.replace(',','.')
df.minimumDepthInMeters = df.minimumDepthInMeters.str.split().str[0]
df.minimumDepthInMeters = df.minimumDepthInMeters.str.split('-').str[0]
df.minimumDepthInMeters = df.minimumDepthInMeters.astype(float)
df.minimumDepthInMeters.mean()
# 13.502396271008395
# Строим гистограмму распределения значений из столбца 'minimumDepthInMeters'
# Аргумент bins=20 задает количество корзинок: чем больше значение, тем детальнее гистограмма
sns.histplot(df.minimumDepthInMeters, bins=20)
# Добавляем сетку на фон графика для лучшего восприятия
plt.grid()
# Подписываем ось X как 'Depth'
plt.xlabel('Depth')
# Подписываем ось Y как 'Number of occurrences'
plt.ylabel('Number of occurrences')
sns.histplot(df.minimumDepthInMeters, bins=20)
plt.grid()
# Меняем масштаб оси Y на логарифмический
plt.yscale('log')
plt.xlabel('Depth')
plt.ylabel('Number of occurrences')
# Получаем столбец 'class' — он содержит класс животных
df['class']
# 0 Anthozoa
# 1 Anthozoa
# 2 Anthozoa
# 3 Anthozoa
# 4 Anthozoa
# ...
# 3091 Copepoda
# 3101 Copepoda
# 3102 Copepoda
# 3103 Copepoda
# 3222 Copepoda
# Name: class, Length: 10244, dtype: object
# Добавляем новый столбец 'is_copepode', который отмечает строки, где класс равен 'Hexanauplia'.
# Операция df['class'] == 'Hexanauplia' возвращает серию True/False.
# Эти булевы значения записываются в новый столбец, создавая логический признак
df['is_copepode'] = df['class'] == 'Hexanauplia'
df.query('is_copepode == True').shape
# (4296, 35)
# Вывести содержимое колонки 'associatedOccurrences'
print(df['associatedOccurrences'])
# 0 NaN
# 1 NaN
# 2 NaN
# 3 NaN
# 4 NaN
# ...
# 3091 urn:catalog:ZMMSU:COPHEX:1121
# 3101 urn:catalog:ZMMSU:COPHEX:1140
# 3102 urn:catalog:ZMMSU:COPHEX:1145
# 3103 urn:catalog:ZMMSU:COPHEX:1146
# 3222 urn:catalog:ZMMSU:COPHEX:914
# Name: associatedOccurrences, Length: 10244, dtype: object
# Отбираем только те строки, где is_copepode == True
df = (
df.query('is_copepode == True')
# Объединяем датафрейм сам с собой, чтобы "сопоставить" копепод с их хозяевами
.merge(
# Оставляем только нужные столбцы для соединения:
# occurrenceID — уникальный идентификатор,
# associatedOccurrences — ссылка на связанные записи,
# class — таксономический класс
df[['occurrenceID', 'associatedOccurrences', 'class']],
# Соединяем таблицы:
left_on='occurrenceID',
right_on='associatedOccurrences',
# Добавляем суффикс "_host" к столбцам из правой таблицы,
# чтобы отличать их от исходных
suffixes=['', '_host']
)
)
# Группируем датафрейм по комбинации значений в столбцах 'order' и 'class_host'
# .size() считает количество строк (наблюдений) в каждой группе
pair_counts = df.groupby(['order', 'class_host']).size().reset_index(name='count')
# Просматриваем результирующий датафрейм
pair_counts
# order class_host count
# 0 Canuelloida Malacostraca 5
# 1 Cyclopoida Anthozoa 87
# 2 Cyclopoida Ascidiacea 2
# 3 Cyclopoida Asteroidea 487
# 4 Cyclopoida Bivalvia 28
# 5 Cyclopoida Cephalopoda 1
# ...
# 27 Siphonostomatoida Malacostraca 2
# 28 Siphonostomatoida Ophiuroidea 147
# Строим диаграмму рассеяния
sns.scatterplot(
data=pair_counts, # Источник данных — датафрейм pair_counts
x='class_host', # Ось X — таксономический класс хозяина
y='order', # Ось Y — отряд копепод (Copepoda)
size='count' # Размер точек соответствует количеству наблюдений (count)
)
# Добавляем сетку для лучшего восприятия структуры графика
plt.grid()
# Поворачиваем подписи оси X на 90°, чтобы они не перекрывались
plt.xticks(rotation=90)
# Подписываем оси для ясности
plt.xlabel('Host class')
plt.ylabel('Copepoda order')
Когда мы строим график, это нормально, что вначале он получается не таким, каким мы хотим его видеть. Это вполне обыденная вещь. Если писать только минимальную команду sns.scatterplot(), то по дефолту график будет без решетки, которая удобна для восприятия и сразу показывает, к каким значениям относятся, например, границы. Также подписи к классам насладывались бы друг на друга, если бы их не повернули.
Строя график, мы постепенно кастомизируем его, добавляя код. Чем больше вы добавите кода, тем более навороченный будет ваш график. Иногда это необходимо, если вы делаете финальные презентации в журнал. Иногда это не обязательно, если вы быстро хотите посмотреть, как выглядят ваши данные и показать коллегам.
# Считаем количество каждого хозяина в таблице с уникальными ассоциациями
host_counts = df['class_host'].value_counts()
# Берем только хозяев, которые встречаются более 5 раз
hosts = host_counts[host_counts > 5].index
# Фильтруем DataFrame, оставляя только строки, где 'class_host' входит в список популярных хостов
df = df.query('class_host in @hosts')
# Построения боксплотов:
sns.boxplot(
data=df,
x='class_host',
y='minimumDepthInMeters',
)
# Код для украшения:
plt.grid()
plt.xticks(rotation=90);
plt.xlabel('Host class')
plt.ylabel('Depth')
Все вышеперечисленные графики можно было построить с помощью библиотеки matplotlib. Но мы использовали библиотеку Seaborn (фан-факт читать тут), которая представляет собой надстройку над matplotlib с более красивым стилем и дополнительными возможностями. И у нее работает сайт, на котором представлена галерея графиков.
Некоторые графики, а именно гистограммы, теплокарты, барплоты, — это плоть и кровь анализа данных. К этим графикам люди привыкли больше всего, и они очень обширно встречаются. Некоторые графики более экзотичны, они свойственны либо определенной области науки, либо просто данные, на которых их строят, более редкие.
На сайте Seaborn к каждому графику указан код, который его генерирует. Вам не обязательно всегда прописывать самостоятельно все команды для отстройки. Это нормально найти готовый код, скопировать его, возможно, немножко поменять и использовать. Это хорошая практика, ведь вы не потратите свое время.