Документация Engee

Платформы работы с данными

На следующих трех платформах доступны эффективные методы для работы с DataFrame: DataFramesMeta.jl, DataFrameMacros.jl и Query.jl. Они реализуют функциональность, аналогичную dplyr или LINQ.

DataFramesMeta.jl

Пакет DataFramesMeta.jl предоставляет удобный и быстрый интерфейс на основе макросов для работы с DataFrame. Приведенные ниже инструкции предназначены для DataFramesMeta.jl версии 0.10.0.

Сначала установите пакет DataFramesMeta.jl.

using Pkg
Pkg.add("DataFramesMeta")

Основное преимущество пакета заключается в том, что он предлагает более удобный синтаксис для функций преобразования transform, select и combine за счет использования макросов @transform, @select, @combine и др.

DataFramesMeta.jl также повторно экспортирует макрос @chain из Chain.jl, позволяя пользователям передавать выходные данные одного преобразования в качестве входных данных для другого, как это делается с помощью |> и %>% в R.

Ниже приводится несколько примеров использования пакета.

Сначала мы разделим на подмножество строки исходного фрейма данных с помощью логического условия и выберем два его столбца, переименовав один из них:

julia> using DataFramesMeta

julia> df = DataFrame(name=["John", "Sally", "Roger"],
                      age=[54.0, 34.0, 79.0],
                      children=[0, 2, 4])
3×3 DataFrame
 Row │ name    age      children
     │ String  Float64  Int64
─────┼───────────────────────────
   1 │ John       54.0         0
   2 │ Sally      34.0         2
   3 │ Roger      79.0         4

julia> @chain df begin
           @rsubset :age > 40
           @select(:number_of_children = :children, :name)
       end
2×2 DataFrame
 Row │ number_of_children  name
     │ Int64               String
─────┼────────────────────────────
   1 │                  0  John
   2 │                  4  Roger

В следующих примерах мы покажем, что DataFramesMeta.jl также поддерживает шаблон «разделение-применение-объединение»:

julia> df = DataFrame(key=repeat(1:3, 4), value=1:12)
12×2 DataFrame
 Row │ key    value
     │ Int64  Int64
─────┼──────────────
   1 │     1      1
   2 │     2      2
   3 │     3      3
   4 │     1      4
   5 │     2      5
   6 │     3      6
   7 │     1      7
   8 │     2      8
   9 │     3      9
  10 │     1     10
  11 │     2     11
  12 │     3     12

julia> @chain df begin
           @rsubset :value > 3
           @by(:key, :min = minimum(:value), :max = maximum(:value))
           @select(:key, :range = :max - :min)
        end
3×2 DataFrame
 Row │ key    range
     │ Int64  Int64
─────┼──────────────
   1 │     1      6
   2 │     2      6
   3 │     3      6

julia> @chain df begin
           groupby(:key)
           @transform :value0 = :value .- minimum(:value)
       end
12×3 DataFrame
 Row │ key    value  value0
     │ Int64  Int64  Int64
─────┼──────────────────────
   1 │     1      1       0
   2 │     2      2       0
   3 │     3      3       0
   4 │     1      4       3
   5 │     2      5       3
   6 │     3      6       3
   7 │     1      7       6
   8 │     2      8       6
   9 │     3      9       6
  10 │     1     10       9
  11 │     2     11       9
  12 │     3     12       9

Более подробную информацию об использовании этого пакета можно найти на странице о DataFramesMeta.jl на сайте GitHub.

DataFrameMacros.jl

DataFrameMacros.jl является альтернативой DataFramesMeta.jl с дополнительным акцентом на эффективные решения для преобразования нескольких столбцов за раз. Ниже приведены инструкции для DataFrameMacros.jl версии 0.3.

Сначала установите пакет DataFrameMacros.jl:

using Pkg
Pkg.add("DataFrameMacros")

В DataFrameMacros.jl все макросы, кроме макроса @combine, по умолчанию выполняются построчно. Также существует макрос @groupby, который позволяет быстро создавать группирующие столбцы, используя тот же синтаксис, что и @transform, для группировки по новым столбцам без необходимости записывать их дважды.

В примере ниже можно также увидеть некоторые возможности DataFrameMacros.jl для работы с несколькими столбцами, где mean применяется сразу к обоим столбцам age, выбирая их с помощью регулярного выражения r"age". Для получения новых имен столбцов используется заполнитель "{}", который объединяет преобразованные имена столбцов в строку.

julia> using DataFrames, DataFrameMacros, Chain, Statistics

julia> df = DataFrame(name=["John", "Sally", "Roger"],
                      age=[54.0, 34.0, 79.0],
                      children=[0, 2, 4])
3×3 DataFrame
 Row │ name    age      children
     │ String  Float64  Int64
─────┼───────────────────────────
   1 │ John       54.0         0
   2 │ Sally      34.0         2
   3 │ Roger      79.0         4

julia> @chain df begin
           @transform :age_months = :age * 12
           @groupby :has_child = :children > 0
           @combine "mean_{}" = mean({r"age"})
       end
2×3 DataFrame
 Row │ has_child  mean_age  mean_age_months
     │ Bool       Float64   Float64
─────┼──────────────────────────────────────
   1 │     false      54.0            648.0
   2 │      true      56.5            678.0

Кроме того, с помощью синтаксиса {{ }} можно ссылаться на группу из нескольких столбцов как на единое целое, например для выполнения по ним агрегирований. В следующем примере первый квартал сравнивается с максимальным значением трех остальных:

julia> df = DataFrame(q1 = [12.0, 0.4, 42.7],
                      q2 = [6.4, 2.3, 40.9],
                      q3 = [9.5, 0.2, 13.6],
                      q4 = [6.3, 5.4, 39.3])
3×4 DataFrame
 Row │ q1       q2       q3       q4
     │ Float64  Float64  Float64  Float64
─────┼────────────────────────────────────
   1 │    12.0      6.4      9.5      6.3
   2 │     0.4      2.3      0.2      5.4
   3 │    42.7     40.9     13.6     39.3

julia> @transform df :q1_best = :q1 > maximum({{Not(:q1)}})
3×5 DataFrame
 Row │ q1       q2       q3       q4       q1_best
     │ Float64  Float64  Float64  Float64  Bool
─────┼─────────────────────────────────────────────
   1 │    12.0      6.4      9.5      6.3     true
   2 │     0.4      2.3      0.2      5.4    false
   3 │    42.7     40.9     13.6     39.3     true

Query.jl

Пакет Query.jl предоставляет расширенные возможности работы с данными для DataFrame (и многих других структур данных). В этом разделе приводятся краткие вводные сведения о пакете. Более полная документация по пакету Query.jl содержится здесь. Инструкции ниже предназначены для Query.jl версии 1.0.0.

Сначала установите пакет Query.j:

using Pkg
Pkg.add("Query")

Запрос начинается с макроса @from и состоит из серии команд запроса. В Query.jl доступны команды для фильтрации, проецирования, объединения, формирования плоской структуры и группировки данных из DataFrame. Запрос может возвращать итератор, или же можно материализовать результаты запроса в различные структуры данных, включая новый DataFrame.

Далее приведен простой пример запроса.

julia> using DataFrames, Query

julia> df = DataFrame(name=["John", "Sally", "Roger"],
                      age=[54.0, 34.0, 79.0],
                      children=[0, 2, 4])
3×3 DataFrame
 Row │ name    age      children
     │ String  Float64  Int64
─────┼───────────────────────────
   1 │ John       54.0         0
   2 │ Sally      34.0         2
   3 │ Roger      79.0         4

julia> q1 = @from i in df begin
            @where i.age > 40
            @select {number_of_children=i.children, i.name}
            @collect DataFrame
       end
2×2 DataFrame
 Row │ number_of_children  name
     │ Int64               String
─────┼────────────────────────────
   1 │                  0  John
   2 │                  4  Roger

Запрос начинается с макроса @from. Первый аргумент i — это имя переменной диапазона, которая будет использоваться для ссылки на отдельную строку в последующих командах запроса. Следующий аргумент df — это источник данных, к которому нужно выполнить запрос. Команда @where в этом запросе отфильтрует исходные данные, применив условие фильтрации i.age > 40. Будут отфильтрованы все строки, в которых значение столбца age не превышает 40. Затем команда @select проецирует столбцы исходных данных в новую структуру столбцов. В данном примере применяется три определенных изменения: 1) сохраняется только подмножество столбцов в исходном DataFrame, т. е. столбец age не будет частью преобразованных данных; 2) изменяется порядок двух выбранных столбцов; и 3) один из выбранных столбцов переименовывается с children на number_of_children. Для этого в примере запроса используется синтаксис {}. {} в выражении Query.jl создает экземпляр NamedTuple, то есть является заполнителем для написания @NT(number_of_children=>i.children, name=>i.name). Оператор @collect определяет структуру данных, которую возвращает запрос. В этом примере результаты возвращаются в виде DataFrame.

Запрос без оператора @collect возвращает стандартный итератор julia, который можно использовать с любой обычной конструкцией языка julia, поддерживающей работу с итераторами. Следующий код возвращает итератор julia для результатов запроса:

julia> q2 = @from i in df begin
                   @where i.age > 40
                   @select {number_of_children=i.children, i.name}
              end; # подавляет вывод типа итератора

Можно перебрать результаты с помощью стандартного оператора julia for:

julia> total_children = 0
0

julia> for i in q2
           global total_children += i.number_of_children
       end

julia> total_children
4

Или же можно использовать выделение для извлечения имени подмножества строк:

julia> y = [i.name for i in q2 if i.number_of_children > 0]
1-element Vector{String}:
 "Roger"

Последний пример (извлечение только имени и применение второго фильтра), конечно, можно полностью выразить в виде выражения запроса:

julia> q3 = @from i in df begin
            @where i.age > 40 && i.children > 0
            @select i.name
            @collect
       end
1-element Vector{String}:
 "Roger"

Запрос, заканчивающийся оператором @collect без указания типа, материализует результаты запроса в массив. Обратите внимание на отличие в операторе @select: Все предыдущие запросы использовали синтаксис {} в операторе @select, чтобы вывести результаты в табличном формате. А последний запрос просто выбирает одно значение из каждой строки в операторе @select.

Эти примеры лишь поверхностно описывают то, что можно сделать с помощью Query.jl, а заинтересованный читатель может обратиться к документации Query.jl за дополнительной информацией.