Платформы работы с данными
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
.