DataFrame: структура и способы создания

DataFrame — это не «таблица как в Excel», а упорядоченный набор столбцов-Series, разделяющих общий индекс строк.

DataFrame — двумерная структура: словарь, где ключи — имена столбцов, значения — Series, и у всех столбцов один общий индекс строк.

Как DataFrame устроен внутри

Проще всего думать о DataFrame как о словаре столбцов: имя столбца → Series. У всех этих Series общий индекс строк, поэтому строки выровнены между столбцами. Важное следствие: каждый столбец имеет свой dtype. В одной таблице спокойно живут целочисленный столбец, столбец строк и столбец дат — в отличие от numpy-массива, где тип один на всё.

Есть две оси: axis=0 — строки (вниз), axis=1 — столбцы (вправо). Многие методы принимают axis, и путаница здесь — частый источник ошибок. Запомните: операция «по строкам» (axis=0) идёт вертикально и схлопывает строки, давая результат на столбец; «по столбцам» (axis=1) идёт горизонтально.

Лучший способ запомнить: axis указывает, какая ось исчезает в результате. df.sum(axis=0) схлопывает строки — остаётся по одному числу на столбец (сумма каждого столбца). df.sum(axis=1) схлопывает столбцы — остаётся по числу на строку (сумма по строке). Та же логика у drop: df.drop("город", axis=1) убирает столбец, df.drop(0, axis=0) — строку. Эта пара осей пронизывает весь pandas, поэтому интуиция «axis = ось, вдоль которой сворачиваем» окупается многократно.

Создание из словаря: самый частый способ

Чаще всего DataFrame собирают из словаря «столбец → список значений»:

import pandas as pd

df = pd.DataFrame({
    "город": ["Москва", "Казань", "Сочи"],
    "население": [13_000_000, 1_300_000, 470_000],
    "море": [False, False, True],
})
print(df)
#      город  население   море
# 0   Москва   13000000  False
# 1   Казань    1300000  False
# 2     Сочи     470000   True

print(df.dtypes)
# город        object
# население     int64
# море           bool
# dtype: object

Ключи словаря стали именами столбцов, индекс строк pandas создал автоматически (0, 1, 2). Каждый столбец получил свой тип. Можно задать индекс явно через index= — например, кодом города.

Создание из списка словарей (записей)

Когда данные приходят «по строкам» (например, из JSON-API), удобен список словарей — каждый словарь это одна строка:

rows = [
    {"товар": "мышь", "цена": 990, "шт": 3},
    {"товар": "клавиатура", "цена": 2500, "шт": 1},
    {"товар": "монитор", "цена": 18000},  # "шт" пропущено
]

# Соберём "DataFrame" вручную: объединим все ключи в набор столбцов
columns = []
for r in rows:
    for k in r:
        if k not in columns:
            columns.append(k)

print("столбцы:", columns)
for i, r in enumerate(rows):
    line = [str(r.get(c, "NaN")) for c in columns]
    print(i, line)

Вывод:

столбцы: ['товар', 'цена', 'шт']
0 ['мышь', '990', '3']
1 ['клавиатура', '2500', '1']
2 ['монитор', '18000', 'NaN']

Именно так pd.DataFrame(rows) и поступает: собирает объединение всех ключей в столбцы, а недостающие значения заполняет NaN. В реальном pandas строка с пропущенной «шт» получила бы NaN, и столбец «шт» стал бы float64.

Из numpy-массива и из файлов

Из двумерного numpy-массива создаётся DataFrame с числовыми позициями вместо имён — поэтому почти всегда стоит передать columns=:

import numpy as np
arr = np.arange(6).reshape(3, 2)
df = pd.DataFrame(arr, columns=["a", "b"])

# Из файлов — самый частый источник в реальной работе:
df = pd.read_csv("sales.csv")          # CSV
df = pd.read_excel("report.xlsx")      # Excel
df = pd.read_parquet("data.parquet")   # колоночный формат
df = pd.read_json("records.json")      # JSON

Чтение файлов мы детально разберём в последнем разделе — там много важных параметров (типы, даты, кодировки). Пока запомните, что read_* возвращают готовый DataFrame.

Доступ к столбцам и строкам

Один столбец достают квадратными скобками или через атрибут, и результат — это Series (то самое выравнивание по индексу строк, которое мы обсуждали):

df["население"]        # столбец как Series
df[["город", "море"]]  # несколько столбцов как DataFrame (двойные скобки!)
df.население           # атрибутный доступ — удобно, но хрупко

Важная тонкость: df["x"] возвращает Series (один столбец), а df[["x"]] — DataFrame из одного столбца. Это разные типы, и путаница приводит к ошибкам в дальнейшем коде. Атрибутный доступ df.население работает, только если имя столбца — корректный идентификатор Python и не конфликтует с методом DataFrame (есть df.count — это метод, а не ваш столбец «count»), поэтому в надёжном коде предпочитают скобки. Создание нового столбца — просто присваивание: df["плотность"] = df["население"] / df["площадь"].

Просмотр и первичный осмотр данных

Первое, что делают с новым DataFrame, — смотрят на него. Эти методы стоит выучить как рефлекс:

МетодЧто показывает
df.head(n) / df.tail(n)первые / последние n строк (по умолчанию 5)
df.shapeкортеж (строк, столбцов)
df.dtypesтип каждого столбца
df.info()столбцы, типы, число непустых значений, память
df.describe()статистика по числовым столбцам (count, mean, std, квартили)
df.columns / df.indexимена столбцов / метки строк
df.memory_usage(deep=True)память по столбцам (deep — точно для строк)

info() — главный диагностический метод: за один взгляд видно, где пропуски (по числу non-null) и не «уплыли» ли типы (числа как object — признак грязных данных). describe() быстро выявляет аномалии: отрицательный возраст, цену в триллион, нулевой минимум там, где его быть не должно.

Подводные камни

  • Порядок столбцов из словаря. В современном Python и pandas порядок ключей словаря сохраняется, но не полагайтесь на это для критичной логики — обращайтесь к столбцам по имени, а не по позиции.
  • object вместо числа. Если dtypes показывает object там, где ждёте число, в данных есть нечисловые значения (пробелы, «—», запятая вместо точки). Это надо чистить (раздел 3).
  • describe() игнорирует нечисловые столбцы по умолчанию. Для строковых используйте df.describe(include="object") или include="all".
  • memory_usage без deep=True недооценивает строки: он считает только указатели, а не сами строки в памяти Python.

Лучшие практики

  • После загрузки сразу вызывайте df.info() и df.describe(include="all") — это экономит часы отладки.
  • Давайте осмысленный индекс (set_index("id")), если в данных есть естественный ключ строки — выравнивание и объединения станут проще.
  • Обращайтесь к столбцам по имени; не завязывайтесь на их порядок.

Итог

  • DataFrame — упорядоченный словарь столбцов-Series с общим индексом строк.
  • У каждого столбца свой dtype — это сила pandas по сравнению с numpy.
  • Создаётся из dict, списка записей, массива или файла; недостающие значения дают NaN.
  • info(), describe(), shape, dtypes — обязательный первичный осмотр.
Проверьте себя
1. Чем DataFrame принципиально отличается от двумерного numpy-массива?
ADataFrame всегда быстрее
BВ DataFrame у каждого столбца может быть свой dtype, а в numpy тип один на весь массив
Cnumpy не умеет хранить числа
DDataFrame не может иметь именованные столбцы
2. Какой метод за один вызов покажет типы столбцов, число непустых значений и расход памяти?
Adf.describe()
Bdf.head()
Cdf.info()
Ddf.shape
3. При создании DataFrame из списка словарей в одной записи отсутствует ключ. Что произойдёт?
AЗапись будет отброшена
BВозникнет ошибка
CНа месте отсутствующего значения будет NaN, столбец станет float
DЗначение возьмётся из соседней строки
Поддержать проект