Введение
В современной дата-инженерии работа с данными неразрывно связана с различными форматами файлов. Каждый формат имеет свои особенности, преимущества и области применения. В этой статье мы рассмотрим наиболее популярные форматы, научимся с ними работать и поймем, когда какой формат лучше использовать.
Я подготовил примеры кода, которые помогут нам на практике разобраться с каждым форматом. Весь код доступен в моём репозитории.
Тестовые данные
Мы сейчас с вами создадим функцию, которая позволит генерировать пользователей для дальнейшей работы.
Если вы хотите использовать другие данные или использовать настоящие данные, то можете обратиться к моей статье Pet-проекты и данные для Data-Engineer
Для сравнения форматов создадим функцию-генератор данных:
import datetime import uuid import pandas as pd from faker import Faker def generate_users(size_of_generate: int = 1000) -> pd.DataFrame: """ Функция-генератор. Позволяет создать случайных пользователей для работы с ними. :param size_of_generate: Количество пользователей для генерации. :return: pd.DataFrame с данными. """ fake = Faker(locale="ru_RU") list_of_dict = [] for _ in range(size_of_generate): dict_ = { "id": str(uuid.uuid4()), "created_at": fake.date_time_ad( start_datetime=datetime.date(year=2024, month=1, day=1), end_datetime=datetime.date(year=2025, month=1, day=1), ), "updated_at": fake.date_time_ad( start_datetime=datetime.date(year=2024, month=1, day=1), end_datetime=datetime.date(year=2025, month=1, day=1), ), "first_name": fake.first_name(), "last_name": fake.last_name(), "middle_name": fake.middle_name(), "birthday": fake.date_time_ad( start_datetime=datetime.date(year=1980, month=1, day=1), end_datetime=datetime.date(year=2005, month=1, day=1), ), "email": fake.email(), "city": fake.city(), } list_of_dict.append(dict_) return pd.DataFrame(data=list_of_dict)
Parquet (.parquet)
Apache Parquet – колоночный формат хранения данных, оптимизированный для работы с большими наборами данных.
Рекомендую ознакомиться с видео Parquet File Format — Explained to a 5 Year Old! в нём хорошо рассказано про .parquet.
Преимущества
-
Является «стандартом» в дата-инженерии
-
Эффективное сжатие данных
-
Быстрое чтение отдельных колонок
-
Поддержка вложенных структур данных
-
Хорошая интеграция со многими инструментами в дата-инженерии и не только
Недостатки
-
Бинарный формат (нельзя прочитать в текстовом редакторе)
-
Может быть избыточным для маленьких наборов данных
Пример записи
from generate_data import generate_users df = generate_users() df.to_parquet(path="../data/data.parquet")
Пример чтения
Мы можем прочитать весь файл:
import pandas as pd df = pd.read_parquet(path="../data/data.parquet") print(df)
Или прочитать только отдельные колонки:
import pandas as pd df = pd.read_parquet(path="../data/data.parquet", columns=["id", "city"]) print(df)
CSV (.csv)
Comma-Separated Values – текстовый формат для представления табличных данных.
Преимущества
-
Простой и понятный формат
-
Человекочитаемый
-
Поддерживается практически везде
-
Легко создавать и редактировать
Недостатки
-
Неэффективное использование места
-
Медленное чтение больших файлов
-
Проблемы с типами данных
-
Сложности с обработкой специальных символов
Пример записи
Без сжатия:
from generate_data import generate_users df = generate_users() df.to_csv( path_or_buf='../data/data.csv', index=False, )
С сжатием:
from generate_data import generate_users df = generate_users() df.to_csv( path_or_buf="../data/data.csv.gz", compression="gzip", index=False, )
Пример чтения
Без сжатия:
import pandas as pd df = pd.read_csv('../data/data.csv') print(df)
С сжатием:
import pandas as pd df = pd.read_csv('../data/data.csv.gz') print(df)
Также мы сразу при чтении можем привести к нужным нам типам данных через конструкцию dtype (необходимо использовать типы NumPy).
Изначально в .csv всё хранится с типом object:
id object created_at object updated_at object first_name object last_name object middle_name object birthday object email object city object
Но мы при чтении к примеру можем распарсить даты:
import pandas as pd df = pd.read_csv( filepath_or_buffer="../data/data.csv", parse_dates=["created_at"], ) print(df.dtypes)
И получим следующий результат после запуска:
id object created_at datetime64[ns] updated_at object first_name object last_name object middle_name object birthday object email object city object
JSON (.json)
JavaScript Object Notation – текстовый формат для хранения структурированных данных.
Преимущества
-
Человекочитаемый формат
-
Поддержка сложных структур данных
-
Широкая поддержка во всех языках
-
Удобен для API и веб-сервисов
Недостатки
-
Избыточность из-за повторения ключей
-
Большой размер файлов
-
Медленнее бинарных форматов
Пример записи
from generate_data import generate_users df = generate_users() df.to_json(path_or_buf="../data/data.json")
Пример чтения
import pandas as pd df = pd.read_json("../data/data.json") print(df)
Avro (.avro)
Apache Avro – компактный бинарный формат с поддержкой схем данных.
Преимущества
-
Компактное хранение
-
Встроенная поддержка схем
-
Хорошая совместимость со схемами
-
Популярен в Apache Kafka
Недостатки
-
Сложнее в использовании
-
Требует определения схемы
-
Меньше инструментов поддержки
Пример записи
import json import avro.schema from avro.datafile import DataFileWriter from avro.io import DatumWriter from generate_data import generate_users df = generate_users(size_of_generate=1) # Определение схемы schema = { "type": "record", "name": "user", "fields": [ {"name": "id", "type": "string"}, {"name": "created_at", "type": "string"}, {"name": "updated_at", "type": "string"}, {"name": "first_name", "type": "string"}, {"name": "middle_name", "type": "string"}, {"name": "birthday", "type": "string"}, {"name": "email", "type": "string"}, {"name": "city", "type": "string"}, ], } # Преобразуем datetime колонки в строки datetime_columns = ["created_at", "updated_at", "birthday"] for col in datetime_columns: df[col] = df[col].astype(str) # Запись в Avro with DataFileWriter( open("../data/data.avro", "wb"), DatumWriter(), avro.schema.parse(json.dumps(schema)), ) as writer: for _, row in df[[ "id", "created_at", "updated_at", "first_name", "last_name", "middle_name", "birthday", "email", "city"]].iterrows(): writer.append({ "id": row["id"], "created_at": row["created_at"], "updated_at": row["updated_at"], "first_name": row["first_name"], "middle_name": row["middle_name"], "birthday": row["birthday"], "email": row["email"], "city": row["city"], })
Пример чтения
from avro.datafile import DataFileReader from avro.io import DatumReader with DataFileReader( open("../data/data.avro", "rb"), DatumReader(), ) as reader: for record in reader: print(record)
Важно: в примерах выше я записал и прочитал одну строку, потому что формат .avro не используется для пакетной передачи данных. Он чаще всего встречается при передачи сообщений в Kafka или других брокерах сообщений.
Если вы не знаете что такое Kafka, но хотели бы с ней познакомиться, то можете изучить мою статью Инфраструктура для data engineer Kafka.
Сравнение форматов
Давайте сравним размеры файлов:
import os import pandas as pd def measure_formats() -> pd.DataFrame: """ Функция, которая позволяет посчитать объём файлов в разных форматах. :return: pd.DataFrame c данными. """ results = [] # Размеры файлов files = { "parquet": "../data/data.parquet", "csv": "../data/data.csv", "csv.gz": "../data/data.csv.gz", "json": "../data/data.json", } for format_name, filename in files.items(): if os.path.exists(filename): size = os.path.getsize(filename) results.append({ "format": format_name, "size_mb": size / (1024 * 1024), }) return pd.DataFrame(results) print(measure_formats())
Для 1 строки в форматах:
format size_mb 0 parquet 0.006000 1 csv 0.000274 2 csv.gz 0.000257 3 json 0.000444 4 avro 0.000598
Для 1’000 строк в форматах:
format size_mb 0 parquet 0.103770 1 csv 0.205385 2 csv.gz 0.085176 3 json 0.356647
Для 1’000’000 строк в форматах
format size_mb 0 parquet 73.052988 1 csv 205.540927 2 csv.gz 58.519802 3 json 382.720285
Заключение
В этой статье мы рассмотрели основные форматы файлов, используемые в дата-инженерии. Каждый формат имеет свои сильные и слабые стороны:
-
.parquetотлично подходит для аналитических запросов и работы с большими данными. -
.csvостается универсальным форматом для обмена данными. -
.jsonнезаменим в веб-разработке и API. -
.avroпрекрасно работает в распределенных системах. При выборе формата стоит учитывать: -
Объем данных.
-
Требования к производительности.
-
Необходимость человекочитаемости.
-
Интеграцию с другими системами.
-
Требования к схеме данных.
В реальных проектах часто используется комбинация форматов – например, .json для API, .parquet для хранения исторических данных, и .csv для отчетов. Главное – выбирать формат исходя из конкретных требований задачи.
Стоит также упомянуть, что я перечислил только популярные форматы, с которыми чаще всего встречается дата-инженер.
Существуют ещё следующие форматы данных:
-
.log– представляют собой текстовые файлы, которые используются для записи логов (журналов) различных приложений, систем и серверов. -
.orc(Optimized Row Columnar) – оптимизированный колоночный формат, изначально разработанный для Hive. -
.xml(eXtensible Markup Language) — разметка, которая описывает данные с помощью тегов. Хотя менее популярен из-за своей громоздкости по сравнению с.json,.xmlчасто используется в системах, требующих строгого описания структуры данных. -
etc
Также если вам необходима консультация/менторство/мок-собеседование и другие вопросы по дата-инженерии, то вы можете обращаться ко мне. Все контакты указаны по ссылке.
ссылка на оригинал статьи https://habr.com/ru/articles/859968/
Добавить комментарий