Введение

Вы когда-нибудь имели дело с набором данных, содержащим более 10 миллионов строк? Слишком большой для разумной загрузки в память, но, возможно, слишком маленький для использования базы данных? Как вы справиться с этим?

В этой статье я хочу обсудить, почему поляры могут быть оптимальным выбором для вашего рабочего процесса анализа данных.

Мой набор инструментов по размеру данных

≤ 1000 строк (Excel)

от 1000 до 10 млн строк (dplyr)

от 10 до 50 млн строк (поляры)

≥ 50 млн строк (postgres или снежинка)

Монтаж

Вся информация в этой статье взята с официального сайта и из моего личного опыта работы с пакетом.

Чтобы установить пакет polars для R, вы можете использовать следующую команду.

install.packages("polars", repos = "https://rpolars.r-universe.dev")

Примечание. Пакета еще нет в CRAN, поэтому его необходимо загрузить из репозитория R-Universe.

Настраивать

Прежде чем мы углубимся в детали использования polars, давайте создадим огромный набор случайных данных и сохраним его как файл паркета.

# Large Example Dataset (2GB of Memory)
example_data <-
    wakefield::r_data_frame(
        n = 15000000,
        wakefield::name(replace = TRUE),
        wakefield::sex_inclusive(),
        wakefield::marital(),
        wakefield::dob(),
        wakefield::education(),
        wakefield::employment(),
        wakefield::car(),
        wakefield::political(),
        wakefield::sat(),
        wakefield::income(),
        wakefield::state()
    )

# Parquet File (200 MB of Space)
rio::export(
    example_data,
    "example_data.parquet"
)

rm(example_data)

Набор данных содержит 15 миллионов строк и занимает около 2 ГБ ОЗУ в моей среде R. Однако, когда я экспортирую фрейм данных в файл паркета, он занимает всего около 200 МБ места!

Изучение данных

Одна из моих любимых особенностей поляров — это их способность работать с наборами данных без считывания их в память. Функции scan_parquet() и lazy_csv_reader() создают LazyFrames, которые позволяют вам взаимодействовать с большими наборами данных на диске.

Чтобы проиллюстрировать, как это работает, давайте представим, что мы ничего не знаем о данных примера, которые мы только что создали, и используем поляры для их изучения. Мы можем начать со сканирования файла паркета и использования средства просмотра RStudio для предварительного просмотра первых 5 строк.

# Scan File
example_data <- polars::scan_parquet(
    "example_data.parquet"
)

# Preview
example_data$
    head(5)$
    collect()$
    to_data_frame() |>
    View("Data Preview")

Удачного старта! Этот предварительный просмотр показывает нам первые несколько строк данных, но как мы определяем общее количество строк? Что ж, мы можем сделать это, мгновенно собрав данные в нашу сессию, используя функцию collect() и запросив атрибут высоты.

# Get Row Count
example_data$
    collect()$
    height

#> [1] 1.5e+07

Мы видим, что имеется 1.5e+07 или 15 миллионов строк. Это, конечно, согласуется с нашим предыдущим кодом.

Преобразования данных

Мы также можем использовать поляры для выполнения всевозможных преобразований данных. Например, предположим, что мы хотим разделить столбец «Автомобиль» на два отдельных столбца «Марка» и «Модель».

Вот код, чтобы сделать именно это.

# Split the Cars Column into Make and Model
result <- example_data$
    with_columns(
        polars::pl$col("Car")$
            cast(polars::pl$dtypes$Utf8)$
            str$split(" ")$
            arr$first()$
            alias("Make"),
        polars::pl$col("Car")$
            cast(polars::pl$dtypes$Utf8)$
            str$split(" ")$
            arr$last()$
            alias("Model")
    )

# Preview
result$
    head(5)$
    collect()$
    to_data_frame()[
        c("Car", "Make", "Model")
    ]

#>                   Car    Make       Model
#> 1       Toyota Corona  Toyota      Corona
#> 2   Hornet Sportabout  Hornet  Sportabout
#> 3          Merc 450SL    Merc       450SL
#> 4 Lincoln Continental Lincoln Continental
#> 5           Merc 240D    Merc        240D

Позволь мне объяснить:

  • Я использовал with_columns() для создания новых столбцов.
  • Я использовал polars::pl$col("Car"), чтобы выбрать столбец Car.
  • Я использовал cast(polars::pl$dtypes$Utf8), чтобы изменить тип столбца с категориального на строковый, чтобы я мог использовать функцию разделения.
  • Я использовал str$split(" "), чтобы разделить столбец пробелом на столбец массива списка.
  • Я использовал arr$first() и arr$last() для получения первого и последнего значения массива соответственно.
  • Я использовал alias(), чтобы переименовать результат.

Имейте в виду, что потребовалось несколько проб и ошибок, чтобы выяснить, как именно преобразовать данные. Обратитесь за помощью к документации.

Скорость

polars работает очень быстро. На сканирование файла паркета и преобразование данных ушло менее 5 секунд. Для сравнения, если я прочитаю файл с помощью rio::import() и выполню точно такое же преобразование с помощью dplyr, это займет около 5 минут!

# Import the file
example_data_big <- rio::import(
    "example_data.parquet"
)

# Transform
result <- example_data_big |>
    dplyr::mutate(
        Make = stringr::str_split(
            Car,
            pattern = " ",
            simplify = TRUE
        )[,1],
        Model = stringr::str_split(
            Car,
            pattern = " ",
            simplify = TRUE
        )[,2]
    )

Для гораздо меньших наборов данных я бы предпочел подход dplyr, но в случае больших данных polars меняет правила игры.

Агрегации

Мы также можем выполнить некоторые агрегации данных. Давайте ответим на каждый из следующих бизнес-вопросов:

  • Каков средний доход по штату?
  • Каков средний балл SAT по политическим взглядам?
  • Что считается семейным положением по гендерной идентичности?

Средний доход по штатам

# Mean Income by State
example_data$
    groupby("State")$
    agg(
        polars::pl$col("Income")$mean()
    )$
    collect()$
    to_data_frame()

#>             State   Income
#> 1          Oregon 39903.68
#> 2     Connecticut 39981.70
#> 3            Utah 39898.87
#> 4           Idaho 40148.05
#> 5        Michigan 39949.00
#> 6       Wisconsin 39994.35
#> 7            Ohio 40045.60
#> 8      New Jersey 39970.62
#> 9      Washington 39945.20
#> 10          Texas 40057.42
#> ...

Средний балл SAT по политическим взглядам

# Median SAT by Political Alignment
example_data$
    groupby("Political")$
    agg(
        polars::pl$col("SAT")$median()
    )$
    collect()$
    to_data_frame()

#>      Political  SAT
#> 1     Democrat 1500
#> 2        Green 1500
#> 3  Libertarian 1501
#> 4 Constitution 1501
#> 5   Republican 1500

Семейное положение по полу

# Marital Status by Gender
example_data$
    groupby("Sex", "Marital")$
    agg(
        polars::pl$first()$len()$alias("Count")
    )$
    sort("Sex")$
    collect()$
    to_data_frame()

#>         Sex       Marital   Count
#> 1      Male Never Married 1001467
#> 2      Male      Divorced  999198
#> 3      Male       Married 1000448
#> 4      Male     Separated  999089
#> 5      Male       Widowed 1000569
#> 6    Female Never Married 1002752
#> 7    Female      Divorced  998686
#> 8    Female       Married 1000996
#> 9    Female     Separated  998829
#> 10   Female       Widowed  999365
#> ...

Мы видим, что данные равномерно распределены по каждой категории, что напоминает нам, что они полностью фальшивые.

Каждая из этих агрегатных операций, выполняемая с 15 миллионами строк, выполнялась менее чем за секунду.

Заключение

polars — отличный пакет для анализа данных, и я рекомендую вам попробовать его!

Весь код из этой статьи можно найти здесь: polars-in-the-artic (github.com)

Посетите страницу документации для получения дополнительной помощи: Polars R Package (rpolars.github.io)

До следующего!