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

Сравнение

В этом разделе пакет DataFrames.jl сравнивается с другими фреймворками манипулирования данными в Python, R и Stata.

Пример набора данных можно создать с помощью следующего кода:

using DataFrames
using Statistics

df = DataFrame(grp=repeat(1:2, 3), x=6:-1:1, y=4:9, z=[3:7; missing], id='a':'f')
df2 = DataFrame(grp=[1, 3], w=[10, 11])

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

Обратите внимание, что в представленных ниже сравнениях такие предикаты, как x -> x >= 1, могут быть записаны более кратко: =>(1). Дополнительным преимуществом такой формы является то, что она компилируется только один раз за сеанс Julia (в отличие от формы x -> x >= 1, которая каждый раз определяет новую анонимную функцию).

Сравнение с пакетом pandas в Python

В следующей таблице основные функции пакета DataFrames.jl сравниваются с пакетом Python pandas (версии 1.1.0):

import pandas as pd
import numpy as np

df = pd.DataFrame({'grp': [1, 2, 1, 2, 1, 2],
                   'x': range(6, 0, -1),
                   'y': range(4, 10),
                   'z': [3, 4, 5, 6, 7, None]},
                   index = list('abcdef'))
df2 = pd.DataFrame({'grp': [1, 3], 'w': [10, 11]})

Так как пакет pandas поддерживает множественное индексирование, в примере фрейма данных в качестве индексов строк используются индексы с a по f, а не отдельный столбец id.

Доступ к данным

Операция pandas DataFrames.jl

Индексирование ячеек по позиции

df.iloc[1, 1]

df[2, 2]

Получение среза строк по позиции

df.iloc[1:3]

df[2:3, :]

Получение среза столбцов по позиции

df.iloc[:, 1:]

df[:, 2:end]

Индексирование строк по метке

df.loc['c']

df[findfirst(==('c'), df.id), :]

Индексирование столбцов по метке

df.loc[:, 'x']

df[:, :x]

Получение среза столбцов по метке

df.loc[:, ['x', 'z']]

df[:, [:x, :z]]

df.loc[:, 'x':'z']

df[:, Between(:x, :z)]

Смешанное индексирование

df.loc['c'][1]

df[findfirst(==('c'), df.id), 2]

Обратите внимание, что в Julia применяется индексирование с отсчетом от 1 и включением обеих границ. Для обозначения последнего индекса можно использовать специальное ключевое слово end. Аналогичным образом, для обозначения последнего первого можно использовать ключевое слово begin.

Кроме того, при индексировании фрейма данных с помощью функции findfirst возвращается один объект DataFrameRow. Если id не является уникальным, вместо этого можно использовать функцию findall или логическое индексирование. В этом случае возвращается объект DataFrame, содержащий все соответствующие строки. Следующие две строки кода функционально эквивалентны:

df[findall(==('c'), df.id), :]
df[df.id .== 'c', :]

При индексировании в DataFrames.jl всегда возвращается согласованный и предсказуемый тип. В отличие от этого, функция loc в pandas возвращает объект Series, ели в индексе ровно одно значение 'c', или объект DataFrame, если есть несколько строк со значением индекса 'c'.

Основные операции

Операция pandas DataFrames.jl

Свертка нескольких значений

df['z'].mean(skipna = False)

mean(df.z)

df['z'].mean()

mean(skipmissing(df.z))

df[['z']].agg(['mean'])

combine(df, :z => mean ∘ skipmissing)

Добавление новых столбцов

df.assign(z1 = df['z'] + 1)

transform(df, :z => (v -> v .+ 1) => :z1)

Переименование столбцов

df.rename(columns = {'x': 'x_new'})

rename(df, :x => :x_new)

Выборка и преобразование столбцов

df.assign(x_mean = df['x'].mean())[['x_mean', 'y']]

select(df, :x => mean, :y)

Сортировка строк

df.sort_values(by = 'x')

sort(df, :x)

df.sort_values(by = ['grp', 'x'], ascending = [True, False])

sort(df, [:grp, order(:x, rev = true)])

Удаление отсутствующих строк

df.dropna()

dropmissing(df)

Выбор уникальных строк

df.drop_duplicates()

unique(df)

Обратите внимание, что в pandas значения NaN в аналитических функциях по умолчанию пропускаются. В отличие от этого, функции Julia не пропускают значения NaN. При необходимости вы можете отфильтровать значения NaN перед обработкой, например, так: mean(Iterators.filter(!isnan, x)).

В pandas NaN служит для представления как отсутствующих данных, так и значения с плавающей запятой, не являющегося числом. Для представления отсутствующих данных в Julia определено специальное значение missing. В DataFrames.jl соблюдаются общие правила распространения значений missing, принятые по умолчанию в Julia. При необходимости с помощью функции skipmissing можно удалить отсутствующие данные. Дополнительные сведения см. в разделе Missing Data.

Кроме того, в pandas после применения функции сохраняется исходное имя столбца. В DataFrames.jl по умолчанию добавляется суффикс к имени столбца. Для простоты в приведенных выше примерах имена столбцов не согласованы между pandas и DataFrames.jl (чтобы сохранить прежние имена столбцов, в функции select, transform и combine можно передать именованный аргумент renamecols=false).

Изменяющие операции

Операция pandas DataFrames.jl

Добавление новых столбцов

df['z1'] = df['z'] + 1

df.z1 = df.z .+ 1

transform!(df, :z => (x -> x .+ 1) => :z1)

df.insert(1, 'const', 10)

insertcols!(df, 2, :const => 10)

Переименование столбцов

df.rename(columns = {'x': 'x_new'}, inplace = True)

rename!(df, :x => :x_new)

Сортировка строк

df.sort_values(by = 'x', inplace = True)

sort!(df, :x)

Удаление отсутствующих строк

df.dropna(inplace = True)

dropmissing!(df)

Выбор уникальных строк

df.drop_duplicates(inplace = True)

unique!(df)

В целом в DataFrames.jl соблюдается принятое в Julia соглашение, согласно которому в имени функции, предполагающей изменение данных, используется символ !.

Группирование и агрегирование данных

В DataFrames.jl есть функция groupby, позволяющая выполнять операции с каждой группой по отдельности. Результатом функции groupby является объект GroupedDataFrame, который можно обработать с помощью функции combine, transform или select. В таблице ниже показаны некоторые распространенные сценарии группирования и агрегирования.

Операция pandas DataFrames.jl

Агрегирование по группам

df.groupby('grp')['x'].mean()

combine(groupby(df, :grp), :x => mean)

Переименование столбца после агрегирования

df.groupby('grp')['x'].mean().rename("my_mean")

combine(groupby(df, :grp), :x => mean => :my_mean)

Добавление агрегированных данных в виде столбца

df.join(df.groupby('grp')['x'].mean(), on='grp', rsuffix='_mean')

transform(groupby(df, :grp), :x => mean)

…​и выбор выходных столбцов

df.join(df.groupby('grp')['x'].mean(), on='grp', rsuffix='_mean')[['grp', 'x_mean']]

select(groupby(df, :grp), :id, :x => mean)

Обратите внимание, что в pandas для одномерного результата возвращается объект Series, если только затем не вызывается reset_index. В соответствующих примерах для DataFrames.jl возвращается эквивалентный объект DataFrame. Рассмотрим первый пример:

>>> df.groupby('grp')['x'].mean()
grp
1    4
2    3
Name: x, dtype: int64

Для DataFrames.jl он будет выглядеть так:

julia> combine(groupby(df, :grp), :x => mean)
2×2 DataFrame
 Row │ grp    x_mean
     │ Int64  Float64
─────┼────────────────
   1 │     1      4.0
   2 │     2      3.0

В DataFrames.jl объект GroupedDataFrame поддерживает эффективный поиск ключей. Поэтому частый поиск ключей выполняется с высокой производительностью.

Более сложные команды

В этом разделе представлены более сложные примеры.

Операция pandas DataFrames.jl

Комплексная функция

df[['z']].agg(lambda v: np.mean(np.cos(v)))

combine(df, :z => v -> mean(cos, skipmissing(v)))

Агрегирование нескольких столбцов

df.agg({'x': max, 'y': min})

combine(df, :x => maximum, :y => minimum)

df[['x', 'y']].mean()

combine(df, [:x, :y] .=> mean)

df.filter(regex=("^x")).mean()

combine(df, names(df, r"^x") .=> mean)

Применение функции к нескольким переменным

df.assign(x_y_cor = np.corrcoef(df.x, df.y)[0, 1])

transform(df, [:x, :y] => cor)

Построчная операция

df.assign(x_y_min = df.apply(lambda v: min(v.x, v.y), axis=1))

transform(df, [:x, :y] => ByRow(min))

df.assign(x_y_argmax = df.apply(lambda v: df.columns[v.argmax()], axis=1))

transform(df, AsTable([:x, :y]) => ByRow(argmax))

DataFrame в качестве входного объекта

df.groupby('grp').head(2)

combine(d -> first(d, 2), groupby(df, :grp))

DataFrame в качестве выходного объекта

df[['x']].agg(lambda x: [min(x), max(x)])

combine(df, :x => (x -> (x=[minimum(x), maximum(x)],)) => AsTable)

Обратите внимание, что в pandas после операции groupby порядок строк сохраняется, в то время как в DataFrames.jl они группируются по предоставленным ключам после операции combine, но при выполнении операций select и transform сохраняется исходный порядок строк.

Объединение фреймов данных

DataFrames.jl поддерживает операции объединения наподобие тех, которые используются в реляционных базах данных.

Операция pandas DataFrames.jl

Внутреннее объединение

pd.merge(df, df2, how = 'inner', on = 'grp')

innerjoin(df, df2, on = :grp)

Внешнее объединение

pd.merge(df, df2, how = 'outer', on = 'grp')

outerjoin(df, df2, on = :grp)

Левое объединение

pd.merge(df, df2, how = 'left', on = 'grp')

leftjoin(df, df2, on = :grp)

Правое объединение

pd.merge(df, df2, how = 'right', on = 'grp')

rightjoin(df, df2, on = :grp)

Полуобъединение (фильтрация)

df[df.grp.isin(df2.grp)]

semijoin(df, df2, on = :grp)

Антиобъединение (фильтрация)

df[~df.grp.isin(df2.grp)]

antijoin(df, df2, on = :grp)

При объединении нескольких столбцов как в pandas, так и в DataFrames.jl в качестве значения именованного аргумента on принимается массив.

В случае с полуобъединением и антиобъединением в pandas по-прежнему можно использовать функцию isin при условии, что ключи объединения объединены в кортеж. В DataFrames.jl она работает обычным образом с массивом ключей объединения, указанным в именованном аргументе on.

Сравнение с пакетом dplyr в R

В следующей таблице основные функции пакета DataFrames.jl сравниваются с пакетом R dplyr (версии 1):

df <- tibble(grp = rep(1:2, 3), x = 6:1, y = 4:9,
             z = c(3:7, NA), id = letters[1:6])
Операция dplyr DataFrames.jl

Свертка нескольких значений

summarize(df, mean(x))

combine(df, :x => mean)

Добавление новых столбцов

mutate(df, x_mean = mean(x))

transform(df, :x => mean => :x_mean)

Переименование столбцов

rename(df, x_new = x)

rename(df, :x => :x_new)

Выборка столбцов

select(df, x, y)

select(df, :x, :y)

Выборка и преобразование столбцов

transmute(df, mean(x), y)

select(df, :x => mean, :y)

Выборка строк

filter(df, x >= 1)

subset(df, :x => ByRow(x -> x >= 1))

Сортировка строк

arrange(df, x)

sort(df, :x)

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

Операция dplyr DataFrames.jl

Свертка нескольких значений

summarize(group_by(df, grp), mean(x))

combine(groupby(df, :grp), :x => mean)

Добавление новых столбцов

mutate(group_by(df, grp), mean(x))

transform(groupby(df, :grp), :x => mean)

Выборка и преобразование столбцов

transmute(group_by(df, grp), mean(x), y)

select(groupby(df, :grp), :x => mean, :y)

В таблице ниже сравниваются более сложные команды.

Операция dplyr DataFrames.jl

Комплексная функция

summarize(df, mean(x, na.rm = T))

combine(df, :x => x -> mean(skipmissing(x)))

Преобразование нескольких столбцов

summarize(df, max(x), min(y))

combine(df, :x => maximum, :y => minimum)

summarize(df, across(c(x, y), mean))

combine(df, [:x, :y] .=> mean)

summarize(df, across(starts_with("x"), mean))

combine(df, names(df, r"^x") .=> mean)

summarize(df, across(c(x, y), list(max, min)))

combine(df, ([:x, :y] .=> [maximum minimum])...)

Многомерная функция

mutate(df, cor(x, y))

transform(df, [:x, :y] => cor)

Построчная

mutate(rowwise(df), min(x, y))

transform(df, [:x, :y] => ByRow(min))

mutate(rowwise(df), which.max(c_across(matches("^x"))))

transform(df, AsTable(r"^x") => ByRow(argmax))

DataFrame в качестве входного объекта

summarize(df, head(across(), 2))

combine(d -> first(d, 2), df)

DataFrame в качестве выходного объекта

summarize(df, tibble(value = c(min(x), max(x))))

combine(df, :x => (x -> (value = [minimum(x), maximum(x)],)) => AsTable)

Сравнение с пакетом data.table в R

В следующей таблице основные функции пакета DataFrames.jl сравниваются с пакетом R data.table (версии 1.14.1).

library(data.table)
df  <- data.table(grp = rep(1:2, 3), x = 6:1, y = 4:9,
                  z = c(3:7, NA), id = letters[1:6])
df2 <- data.table(grp=c(1,3), w = c(10,11))
Операция data.table DataFrames.jl

Свертка нескольких значений

df[, .(mean(x))]

combine(df, :x => mean)

Добавление новых столбцов

df[, x_mean:=mean(x) ]

transform!(df, :x => mean => :x_mean)

Переименование столбца (на месте)

setnames(df, "x", "x_new")

rename!(df, :x => :x_new)

Переименование нескольких столбцов (на месте)

setnames(df, c("x", "y"), c("x_new", "y_new"))

rename!(df, [:x, :y] .=> [:x_new, :y_new])

Выборка столбцов в виде фрейма данных

df[, .(x, y)]

select(df, :x, :y)

Выборка столбца в виде вектора

df[, x]

df[!, :x]

Удаление столбцов

df[, -"x"]

select(df, Not(:x))

Удаление столбцов (на месте)

df[, x:=NULL]

select!(df, Not(:x))

Удаление столбцов (на месте)

df[, c("x", "y"):=NULL]

select!(df, Not([:x, :y]))

Выборка и преобразование столбцов

df[, .(mean(x), y)]

select(df, :x => mean, :y)

Выборка строк

df[ x >= 1 ]

filter(:x => >=(1), df)

Сортировка строк (на месте)

setorder(df, x)

sort!(df, :x)

Сортировка строк

df[ order(x) ]

sort(df, :x)

Группирование и агрегирование данных

Операция data.table DataFrames.jl

Свертка нескольких значений

df[, mean(x), by=id ]

combine(groupby(df, :id), :x => mean)

Добавление новых столбцов (на месте)

df[, x_mean:=mean(x), by=id]

transform!(groupby(df, :id), :x => mean)

Выборка и преобразование столбцов

df[, .(x_mean = mean(x), y), by=id]

select(groupby(df, :id), :x => mean, :y)

Более сложные команды

Операция data.table DataFrames.jl

Комплексная функция

df[, .(mean(x, na.rm=TRUE)) ]

combine(df, :x => x -> mean(skipmissing(x)))

Преобразование некоторых строк (на месте)

df[x<=0, x:=0]

df.x[df.x .<= 0] .= 0

Преобразование нескольких столбцов

df[, .(max(x), min(y)) ]

combine(df, :x => maximum, :y => minimum)

df[, lapply(.SD, mean), .SDcols = c("x", "y") ]

combine(df, [:x, :y] .=> mean)

df[, lapply(.SD, mean), .SDcols = patterns("*x") ]

combine(df, names(df, r"^x") .=> mean)

df[, unlist(lapply(.SD, function(x) c(max=max(x), min=min(x)))), .SDcols = c("x", "y") ]

combine(df, ([:x, :y] .=> [maximum minimum])...)

Многомерная функция

df[, .(cor(x,y)) ]

transform(df, [:x, :y] => cor)

Построчная

df[, min_xy := min(x, y), by = 1:nrow(df)]

transform!(df, [:x, :y] => ByRow(min))

df[, argmax_xy := which.max(.SD) , .SDcols = patterns("*x"), by = 1:nrow(df) ]

transform!(df, AsTable(r"^x") => ByRow(argmax))

DataFrame в качестве выходного объекта

df[, .SD[1], by=grp]

combine(groupby(df, :grp), first)

DataFrame в качестве выходного объекта

df[, .SD[which.max(x)], by=grp]

combine(groupby(df, :grp), sdf -> sdf[argmax(sdf.x), :])

Объединение фреймов данных

Операция data.table DataFrames.jl

Внутреннее объединение

merge(df, df2, on = "grp")

innerjoin(df, df2, on = :grp)

Внешнее объединение

merge(df, df2, all = TRUE, on = "grp")

outerjoin(df, df2, on = :grp)

Левое объединение

merge(df, df2, all.x = TRUE, on = "grp")

leftjoin(df, df2, on = :grp)

Правое объединение

merge(df, df2, all.y = TRUE, on = "grp")

rightjoin(df, df2, on = :grp)

Антиобъединение (фильтрация)

df[!df2, on = "grp" ]

antijoin(df, df2, on = :grp)

Полуобъединение (фильтрация)

merge(df1, df2[, .(grp)])

semijoin(df, df2, on = :grp)

Сравнение со Stata (версии 8 и выше)

В следующей таблице основные функции пакета DataFrames.jl сравниваются со Stata.

Операция Stata DataFrames.jl

Свертка нескольких значений

collapse (mean) x

combine(df, :x => mean)

Добавление новых столбцов

egen x_mean = mean(x)

transform!(df, :x => mean => :x_mean)

Переименование столбцов

rename x x_new

rename!(df, :x => :x_new)

Выборка столбцов

keep x y

select!(df, :x, :y)

Выборка строк

keep if x >= 1

subset!(df, :x => ByRow(x -> x >= 1))

Сортировка строк

sort x

sort!(df, :x)

Обратите внимание, что суффикс ! (transform!, select! и т. д.) обеспечивает преобразование фрейма данных на месте, как в Stata.

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

Операция Stata DataFrames.jl

Добавление новых столбцов

egen x_mean = mean(x), by(grp)

transform!(groupby(df, :grp), :x => mean)

Свертка нескольких значений

collapse (mean) x, by(grp)

combine(groupby(df, :grp), :x => mean)

В таблице ниже сравниваются более сложные команды.

Операция Stata DataFrames.jl

Преобразование некоторых строк

replace x = 0 if x <= 0

transform(df, :x => (x -> ifelse.(x .<= 0, 0, x)) => :x)

Преобразование нескольких столбцов

collapse (max) x (min) y

combine(df, :x => maximum, :y => minimum)

collapse (mean) x y

combine(df, [:x, :y] .=> mean)

collapse (mean) x*

combine(df, names(df, r"^x") .=> mean)

collapse (max) x y (min) x y

combine(df, ([:x, :y] .=> [maximum minimum])...)

Многомерная функция

egen z = corr(x y)

transform!(df, [:x, :y] => cor => :z)

Построчная

egen z = rowmin(x y)

transform!(df, [:x, :y] => ByRow(min) => :z)