Сводная таблица по нескольким столбцам в Julia

#dataframe #pivot #julia #pivot-table

#фрейм данных #сводная #julia #сводная таблица

Вопрос:

Я хотел бы создать сводную таблицу для фрейма данных в Julia. Из документации я знаю, что могу сделать это с by помощью и unstack . Например.

 julia> using DataFrames, Random

julia> Random.seed!(42);

julia> df = DataFrame(
           Step = rand(1:3, 15) |> sort,
           Label1 = rand('A':'B', 15) .|> Symbol,
           Label2 = rand('Q':'R', 15) .|> Symbol
       )
15×3 DataFrame
│ Row │ Step  │ Label1 │ Label2 │
│     │ Int64 │ SymbolSymbol │
├─────┼───────┼────────┼────────┤
│ 11     │ A      │ Q      │
│ 21     │ A      │ Q      │
│ 31     │ B      │ R      │
│ 41     │ B      │ R      │
│ 51     │ B      │ Q      │
│ 62     │ B      │ Q      │
│ 72     │ B      │ Q      │
│ 82     │ B      │ R      │
│ 92     │ B      │ R      │
│ 103     │ B      │ R      │
│ 113     │ B      │ Q      │
│ 123     │ B      │ R      │
│ 133     │ A      │ R      │
│ 143     │ B      │ R      │
│ 153     │ B      │ Q      │

julia> unstack(by(df, [:Step, :Label1, :Label2], nrow), :Label1, :nrow)
6×4 DataFrame
│ Row │ Step  │ Label2 │ A       │ B      │
│     │ Int64 │ Symbol │ Int64?  │ Int64? │
├─────┼───────┼────────┼─────────┼────────┤
│ 11     │ Q      │ 21      │
│ 21     │ R      │ missing │ 2      │
│ 32     │ Q      │ missing │ 2      │
│ 42     │ R      │ missing │ 2      │
│ 53     │ Q      │ missing │ 2      │
│ 63     │ R      │ 13 

Теперь, как мне выполнить сводку по двум столбцам, здесь Label1 и Label2, чтобы получить количество строк для каждой комбинации элементов этих двух столбцов? Ожидаемый результат будет примерно таким

 │ Row │ Step  │ AQ      │ AR      │ BQ      │ BR      │
│     │ Int64 │ Int64?  │ Int64?  │ Int64?  │ Int64?  │
├─────┼───────┼─────────┼─────────┼─────────┼─────────┤
│ 112missing12       │
│ 32missingmissing22       │
│ 53missing123 

Заранее спасибо!
Тим

Ответ №1:

Первый — by устарел (руководство будет обновлено через несколько дней, чтобы отразить это), поэтому следует написать:

 julia> unstack(combine(groupby(df, [:Step, :Label1, :Label2]), nrow), :Label1, :nrow)
6×4 DataFrameRowStepLabel2AB      │
│     │ Int64SymbolInt64?  │ Int64? │
├─────┼───────┼────────┼─────────┼────────┤
│ 11Q21      │
│ 21Rmissing2      │
│ 32Qmissing2      │
│ 42Rmissing2      │
│ 53Qmissing2      │
│ 63R13 

Однако, если вам нужно количество строк, я бы предпочел сделать что-то вроде:

 julia> gdf = groupby(df, [:Step, :Label2], sort=true);

julia> lev = unique(df.Label1)
2-element Array{Symbol,1}:
 :A
 :B

julia> combine(gdf, :Label1 .=> [x -> count(==(l), x) for l in lev] .=> lev)
6×4 DataFrame
│ Row │ Step  │ Label2 │ A     │ B     │
│     │ Int64 │ Symbol │ Int64 │ Int64 │
├─────┼───────┼────────┼───────┼───────┤
│ 11     │ Q      │ 21     │
│ 21     │ R      │ 02     │
│ 32     │ Q      │ 02     │
│ 42     │ R      │ 02     │
│ 53     │ Q      │ 02     │
│ 63     │ R      │ 13 

таким образом, у вас 0 нет missing места, где у вас отсутствует значение.

Этот шаблон обобщается на несколько групп:

 julia> gdf = groupby(df, :Step, sort=true);

julia> l1 = unique(df.Label1)
2-element Array{Symbol,1}:
 :A
 :B

julia> l2 = unique(df.Label2)
2-element Array{Symbol,1}:
 :Q
 :R

julia> combine(gdf, [[:Label1, :Label2] =>
                     ((x,y) -> count(((x,y),) -> x==v1 amp;amp; y==v2, zip(x,y))) =>
                     Symbol(v1, v2) for v1 in l1 for v2 in l2])
3×5 DataFrame
│ Row │ Step  │ AQ    │ AR    │ BQ    │ BR    │
│     │ Int64 │ Int64 │ Int64 │ Int64 │ Int64 │
├─────┼───────┼───────┼───────┼───────┼───────┤
│ 112012     │
│ 220022     │
│ 330123 

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

 julia> unstack(combine(groupby(select(df, :Step, [:Label1, :Label2] => ByRow(Symbol) => :Label), [:Step, :Label]), nrow), :Label, :nrow)
3×5 DataFrame
│ Row │ Step  │ AQARBQBR     │
│     │ Int64 │ Int64?  │ Int64?  │ Int64? │ Int64? │
├─────┼───────┼─────────┼─────────┼────────┼────────┤
│ 112       │ missing │ 12      │
│ 22     │ missing │ missing │ 22      │
│ 33     │ missing │ 123 

Тем не менее, я согласен, что это не очень просто. Эта проблема отслеживается в https://github.com/JuliaData/DataFrames.jl/issues/2148 и в связи с этим https://github.com/JuliaData/DataFrames.jl/issues/2205 .

Комментарии:

1. спасибо за подробный ответ. Это именно то, что я искал. В вашем ответе ваши нынешние два подхода и, похоже, предпочитают первый. Не могли бы вы быстро объяснить, почему?

2. вам нужно только знать, как combine это работает, чтобы написать. Для второго вам нужно знать две функции. Вот и все. Когда я думал, как ответить на ваш вопрос combine , моим первым решением было просто использовать (хотя формулы выглядят уродливо, я написал их сразу и без ошибок — поскольку, когда вы понимаете логику, как combine работает «миниязык», это очень мощно).

3. Также обратите внимание, что для цепочки Pipe.jl пакет очень полезен. Я не использовал его в своих ответах, чтобы избежать добавления к нему еще одного уровня сложности, но они выглядели бы намного чище при использовании @pipe макроса (например %>% , в R). Также, возможно, я предвзят, я знаю, что внутренне unstack по существу использует groupby , но таким образом, что делает его более эффективным, чем использование combine .

4. Спасибо за подробное объяснение. Похоже, я должен потратить еще немного времени combine . И да, в моем коде я также сразу использую Pipe.jl 🙂