Практика 1. Базовая работа с форматами данных в Python¶

В этом ноутбуке познакомимся с двумя главными библиотеками для работы с данными:

  • numpy: массивы чисел
  • pandas: таблицы

и научимся читать и сохранять данные в разных форматах (CSV, JSON, Parquet).

Запускайте ячейки по порядку: Shift + Enter.

In [1]:
import numpy as np
import pandas as pd

1. NumPy: массивы¶

In [2]:
a = np.array([1, 2, 3, 4, 5])
print(a)
print("тип:", type(a))
print("размер:", a.shape)
[1 2 3 4 5]
тип: <class 'numpy.ndarray'>
размер: (5,)

Операции применяются сразу ко всему массиву, без циклов:

In [3]:
print(a + 10)
print(a * 2)
print(a ** 2)
[11 12 13 14 15]
[ 2  4  6  8 10]
[ 1  4  9 16 25]
In [4]:
print("сумма:   ", a.sum())
print("среднее: ", a.mean())
print("минимум: ", a.min())
print("максимум:", a.max())
сумма:    15
среднее:  3.0
минимум:  1
максимум: 5

Двумерный массив (матрица): строки — объекты, столбцы — признаки.

In [5]:
m = np.array([
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9],
])
print(m)
print("форма:", m.shape)
print("первая строка:", m[0])
print("первый столбец:", m[:, 0])
print("элемент [1, 2]:", m[1, 2])
[[1 2 3]
 [4 5 6]
 [7 8 9]]
форма: (3, 3)
первая строка: [1 2 3]
первый столбец: [1 4 7]
элемент [1, 2]: 6

Фильтрация по условию:

In [6]:
print(a[a > 2])
[3 4 5]

Полезные способы создать массив:

In [7]:
print(np.zeros(3))
print(np.ones(3))
print(np.arange(0, 10, 2))
print(np.linspace(0, 1, 5))
[0. 0. 0.]
[1. 1. 1.]
[0 2 4 6 8]
[0.   0.25 0.5  0.75 1.  ]

Задание 1¶

Создайте массив чисел от 1 до 10, возведите все числа в квадрат и найдите их сумму.

In [8]:
# ваш код

2. Pandas: таблицы¶

  • Series — один столбец
  • DataFrame — вся таблица
In [9]:
s = pd.Series([10, 20, 30], name="числа")
s
Out[9]:
0    10
1    20
2    30
Name: числа, dtype: int64
In [10]:
df = pd.DataFrame({
    "имя":     ["Аня", "Борис", "Вера", "Глеб", "Дина"],
    "возраст": [23, 31, 27, 45, 38],
    "город":   ["Москва", "Казань", "Москва", "Сочи", "Казань"],
    "зарплата": [90000, 120000, 105000, None, 98000],
})
df
Out[10]:
имя возраст город зарплата
0 Аня 23 Москва 90000.0
1 Борис 31 Казань 120000.0
2 Вера 27 Москва 105000.0
3 Глеб 45 Сочи NaN
4 Дина 38 Казань 98000.0

Осмотр таблицы¶

In [11]:
print("форма:", df.shape)
df.head(3)
форма: (5, 4)
Out[11]:
имя возраст город зарплата
0 Аня 23 Москва 90000.0
1 Борис 31 Казань 120000.0
2 Вера 27 Москва 105000.0
In [12]:
df.info()
<class 'pandas.DataFrame'>
RangeIndex: 5 entries, 0 to 4
Data columns (total 4 columns):
 #   Column    Non-Null Count  Dtype  
---  ------    --------------  -----  
 0   имя       5 non-null      str    
 1   возраст   5 non-null      int64  
 2   город     5 non-null      str    
 3   зарплата  4 non-null      float64
dtypes: float64(1), int64(1), str(2)
memory usage: 388.0 bytes
In [13]:
df.describe()
Out[13]:
возраст зарплата
count 5.000000 4.000000
mean 32.800000 103250.000000
std 8.786353 12737.739203
min 23.000000 90000.000000
25% 27.000000 96000.000000
50% 31.000000 101500.000000
75% 38.000000 108750.000000
max 45.000000 120000.000000

Столбцы и строки¶

In [14]:
print(df["имя"])
0      Аня
1    Борис
2     Вера
3     Глеб
4     Дина
Name: имя, dtype: str
In [15]:
df[["имя", "возраст"]]
Out[15]:
имя возраст
0 Аня 23
1 Борис 31
2 Вера 27
3 Глеб 45
4 Дина 38
In [16]:
df.loc[0]          # строка по номеру индекса
Out[16]:
имя             Аня
возраст          23
город        Москва
зарплата    90000.0
Name: 0, dtype: object
In [17]:
df[df["возраст"] > 30]    # фильтр
Out[17]:
имя возраст город зарплата
1 Борис 31 Казань 120000.0
3 Глеб 45 Сочи NaN
4 Дина 38 Казань 98000.0
In [18]:
df[(df["город"] == "Москва") & (df["возраст"] > 25)]   # несколько условий
Out[18]:
имя возраст город зарплата
2 Вера 27 Москва 105000.0

Новые столбцы и сортировка¶

In [19]:
df["возраст_через_5_лет"] = df["возраст"] + 5
df.sort_values("возраст", ascending=False)
Out[19]:
имя возраст город зарплата возраст_через_5_лет
3 Глеб 45 Сочи NaN 50
4 Дина 38 Казань 98000.0 43
1 Борис 31 Казань 120000.0 36
2 Вера 27 Москва 105000.0 32
0 Аня 23 Москва 90000.0 28

Группировка¶

In [20]:
df.groupby("город")["возраст"].mean()
Out[20]:
город
Казань    34.5
Москва    25.0
Сочи      45.0
Name: возраст, dtype: float64
In [21]:
df["город"].value_counts()
Out[21]:
город
Москва    2
Казань    2
Сочи      1
Name: count, dtype: int64

Пропуски¶

In [22]:
print(df.isna().sum())
имя                    0
возраст                0
город                  0
зарплата               1
возраст_через_5_лет    0
dtype: int64
In [23]:
# заполнить средним
df["зарплата"] = df["зарплата"].fillna(df["зарплата"].mean())
df
Out[23]:
имя возраст город зарплата возраст_через_5_лет
0 Аня 23 Москва 90000.0 28
1 Борис 31 Казань 120000.0 36
2 Вера 27 Москва 105000.0 32
3 Глеб 45 Сочи 103250.0 50
4 Дина 38 Казань 98000.0 43

Задание 2¶

  1. Выберите людей старше 25 лет.
  2. Найдите среднюю зарплату по городам.
  3. Добавьте столбец зарплата_в_год (зарплата × 12).
In [24]:
# ваш код

3. Форматы данных¶

Одни и те же данные можно сохранить в разных форматах:

Формат Что это
CSV обычный текст, значения через запятую
JSON текст со структурой «ключ: значение»
Parquet компактный бинарный формат для больших таблиц; хранит типы столбцов и быстро читается

CSV¶

In [25]:
df.to_csv("people.csv", index=False)
print(open("people.csv", encoding="utf-8").read())
имя,возраст,город,зарплата,возраст_через_5_лет
Аня,23,Москва,90000.0,28
Борис,31,Казань,120000.0,36
Вера,27,Москва,105000.0,32
Глеб,45,Сочи,103250.0,50
Дина,38,Казань,98000.0,43

In [26]:
df_csv = pd.read_csv("people.csv")
df_csv
Out[26]:
имя возраст город зарплата возраст_через_5_лет
0 Аня 23 Москва 90000.0 28
1 Борис 31 Казань 120000.0 36
2 Вера 27 Москва 105000.0 32
3 Глеб 45 Сочи 103250.0 50
4 Дина 38 Казань 98000.0 43

JSON¶

In [27]:
df.to_json("people.json", orient="records", force_ascii=False, indent=2)
print(open("people.json", encoding="utf-8").read())
[
  {
    "имя":"Аня",
    "возраст":23,
    "город":"Москва",
    "зарплата":90000.0,
    "возраст_через_5_лет":28
  },
  {
    "имя":"Борис",
    "возраст":31,
    "город":"Казань",
    "зарплата":120000.0,
    "возраст_через_5_лет":36
  },
  {
    "имя":"Вера",
    "возраст":27,
    "город":"Москва",
    "зарплата":105000.0,
    "возраст_через_5_лет":32
  },
  {
    "имя":"Глеб",
    "возраст":45,
    "город":"Сочи",
    "зарплата":103250.0,
    "возраст_через_5_лет":50
  },
  {
    "имя":"Дина",
    "возраст":38,
    "город":"Казань",
    "зарплата":98000.0,
    "возраст_через_5_лет":43
  }
]
In [28]:
df_json = pd.read_json("people.json")
df_json
Out[28]:
имя возраст город зарплата возраст_через_5_лет
0 Аня 23 Москва 90000 28
1 Борис 31 Казань 120000 36
2 Вера 27 Москва 105000 32
3 Глеб 45 Сочи 103250 50
4 Дина 38 Казань 98000 43

Обычный JSON (не таблица) можно читать стандартным модулем json, он превращает файл в словарь:

In [29]:
import json

data = {"название": "Мой проект", "версия": 1, "теги": ["данные", "python"]}

with open("config.json", "w", encoding="utf-8") as f:
    json.dump(data, f, ensure_ascii=False, indent=2)

with open("config.json", encoding="utf-8") as f:
    loaded = json.load(f)

print(loaded)
print(loaded["теги"][0])
{'название': 'Мой проект', 'версия': 1, 'теги': ['данные', 'python']}
данные

Parquet¶

Parquet — современный формат для хранения таблиц в ML и анализе данных. В отличие от CSV он запоминает типы столбцов и занимает меньше места. Для работы нужна библиотека pyarrow (в Google Colab она уже установлена, локально: pip install pyarrow).

In [30]:
df.to_parquet("people.parquet", index=False)
df_parquet = pd.read_parquet("people.parquet")
df_parquet
Out[30]:
имя возраст город зарплата возраст_через_5_лет
0 Аня 23 Москва 90000.0 28
1 Борис 31 Казань 120000.0 36
2 Вера 27 Москва 105000.0 32
3 Глеб 45 Сочи 103250.0 50
4 Дина 38 Казань 98000.0 43

Из таблицы в numpy и обратно¶

In [31]:
arr = df[["возраст", "зарплата"]].to_numpy()
print(arr)
print(arr.shape)
[[2.3000e+01 9.0000e+04]
 [3.1000e+01 1.2000e+05]
 [2.7000e+01 1.0500e+05]
 [4.5000e+01 1.0325e+05]
 [3.8000e+01 9.8000e+04]]
(5, 2)
In [32]:
pd.DataFrame(arr, columns=["возраст", "зарплата"])
Out[32]:
возраст зарплата
0 23.0 90000.0
1 31.0 120000.0
2 27.0 105000.0
3 45.0 103250.0
4 38.0 98000.0

Массивы numpy тоже можно сохранять:

In [33]:
np.save("arr.npy", arr)
print(np.load("arr.npy"))
[[2.3000e+01 9.0000e+04]
 [3.1000e+01 1.2000e+05]
 [2.7000e+01 1.0500e+05]
 [4.5000e+01 1.0325e+05]
 [3.8000e+01 9.8000e+04]]

Задание 3¶

  1. Сохраните таблицу df в файл result.csv.
  2. Прочитайте его обратно в новую переменную и проверьте, что shape совпадает.
  3. Сохраните её же в result.parquet, прочитайте обратно и сравните размеры файлов result.csv и result.parquet (подсказка: os.path.getsize).
In [34]:
# ваш код

4. Мини-итог¶

  • numpy — быстрые операции над массивами чисел
  • pandas — таблицы: head, info, describe, фильтры, groupby, пропуски
  • Читаем и пишем: read_csv / to_csv, read_json / to_json, read_parquet / to_parquet
  • Прежде чем что-то делать с данными, их нужно осмотреть: shape, head, info, describe