Начало работы

В оставшейся части этого руководства мы будем считать, что вы установили пакет DataFrames и уже ввели using DataFrames, чтобы добавить все соответствующие переменные в текущее пространство имен.

По умолчанию DataFrames.jl ограничивает количество строк и столбцов при отображении фрейма данных в Jupyter Notebook до 25 и 100, соответственно. Это поведение можно переопределить, изменив значения переменных ENV["DATAFRAMES_COLUMNS"] и ENV["DATAFRAMES_ROWS"] на максимальное количество столбцов и строк вывода. Если эти значения равны или меньше 0, будут выведены все столбцы или строки.

Или же можно задать максимальное количество строк фрейма данных для вывода равным 100 и максимальное количество столбцов для вывода равным 1000 для каждого сеанса Julia, используя файл ядра Jupyter (значения 100 и 1000 приведены лишь в качестве примера и могут быть изменены). В этом случае добавьте запись "DATAFRAME_COLUMNS": "1000", "DATAFRAMES_ROWS": "100" в переменную "env" в этом файле ядра Jupyter. Информацию о расположении и спецификации ядер Jupyter смотрите здесь.

Пакет PrettyTables.jl отрисовывает DataFrame в Jupyter Notebook. Пользователи могут настраивать вывод, передавая именованные аргументы kwargs…​ функции show: show(stdout, MIME("text/html"), df; kwargs…​), где df является DataFrame. Здесь можно использовать любой аргумент , поддерживаемый PrettyTables.jl в бэкенде HTML. Например, если пользователь хочет в Jupyter изменить цвет всех чисел меньше 0 на красный, он может выполнить show(stdout, MIME("text/html"), df; highlighters = hl_lt(0, HtmlDecoration(color = "red"))) после using PrettyTables. Более подробную информацию о доступных вариантах можно найти в документации по PrettyTables.jl.

Тип DataFrame

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

julia> using DataFrames

julia> DataFrame(a=1:4, b=["M", "F", "F", "M"]) # конструктор именованных аргументов
4×2 DataFrame
 Row │ a      b
     │ Int64  String
─────┼───────────────
   1 │     1  M
   2 │     2  F
   3 │     3  F
   4 │     4  M

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

julia> DataFrame((a=[1, 2], b=[3, 4])) # конструктор таблиц Tables.jl из именованного кортежа векторов
2×2 DataFrame
 Row │ a      b
     │ Int64  Int64
─────┼──────────────
   1 │     1      3
   2 │     2      4

julia> DataFrame([(a=1, b=0), (a=2, b=0)]) # конструктор таблиц Tables.jl из вектора именованных кортежей
2×2 DataFrame
 Row │ a      b
     │ Int64  Int64
─────┼──────────────
   1 │     1      0
   2 │     2      0

julia> DataFrame("a" => 1:2, "b" => 0) # конструктор пар
2×2 DataFrame
 Row │ a      b
     │ Int64  Int64
─────┼──────────────
   1 │     1      0
   2 │     2      0

julia> DataFrame([:a => 1:2, :b => 0]) # конструктор вектора пар
2×2 DataFrame
 Row │ a      b
     │ Int64  Int64
─────┼──────────────
   1 │     1      0
   2 │     2      0

julia> DataFrame(Dict(:a => 1:2, :b => 0)) # конструктор словаря
2×2 DataFrame
 Row │ a      b
     │ Int64  Int64
─────┼──────────────
   1 │     1      0
   2 │     2      0

julia> DataFrame([[1, 2], [0, 0]], [:a, :b]) # конструктор вектора векторов
2×2 DataFrame
 Row │ a      b
     │ Int64  Int64
─────┼──────────────
   1 │     1      0
   2 │     2      0

julia> DataFrame([1 0; 2 0], :auto) # конструктор матриц
2×2 DataFrame
 Row │ x1     x2
     │ Int64  Int64
─────┼──────────────
   1 │     1      0
   2 │     2      0

Столбцы можно извлекать напрямую (т. е. без копирования), используя df.col, df."col", df[!, :col] или df[!, "col"] (это правило относится к получению данных из фрейма данных, а не к записи данных во фрейм данных). Два последних синтаксиса более гибкие, поскольку позволяют передавать переменную, содержащую имя столбца, а не только литеральное имя. Обратите внимание, что имена столбцов могут быть как символами (записываются как :col, :var"col" или Symbol("col")), так и строками (записываются как "col"). В формах df."col" и :var"col" интерполяция переменных в строку с помощью $ не работает. Столбцы также можно извлекать с помощью целочисленного индекса, определяющего их позицию.

Поскольку df[!, :col] не создает копию, изменение элементов вектора столбцов, возвращаемого этим синтаксисом, повлияет на значения, хранящиеся в исходном df. Для получения копии столбца используйте df[:, :col]: изменение вектора, возвращаемого этим синтаксисом, не изменяет df.

julia> df = DataFrame(A=1:4, B=["M", "F", "F", "M"])
4×2 DataFrame
 Row │ A      B
     │ Int64  String
─────┼───────────────
   1 │     1  M
   2 │     2  F
   3 │     3  F
   4 │     4  M

julia> df.A
4-element Vector{Int64}:
 1
 2
 3
 4

julia> df."A"
4-element Vector{Int64}:
 1
 2
 3
 4

julia> df.A === df[!, :A]
true

julia> df.A === df[:, :A]
false

julia> df.A == df[:, :A]
true

julia> df.A === df[!, "A"]
true

julia> df.A === df[:, "A"]
false

julia> df.A == df[:, "A"]
true

julia> df.A === df[!, 1]
true

julia> df.A === df[:, 1]
false

julia> df.A == df[:, 1]
true

julia> firstcolumn = :A
:A

julia> df[!, firstcolumn] === df.A
true

julia> df[:, firstcolumn] === df.A
false

julia> df[:, firstcolumn] == df.A
true

Имена столбцов можно получить в виде строк с помощью функции names:

julia> names(df)
2-element Vector{String}:
 "A"
 "B"

Вы также можете фильтровать имена столбцов, передавая условие селектора столбцов в качестве второго аргумента. См. docstring names с подробным списком доступных условий. Приведем несколько примеров:

julia> names(df, r"A") # селектор регулярных выражений
1-element Vector{String}:
 "A"

julia> names(df, Int) # селектор, использующий тип элементов столбцов
1-element Vector{String}:
 "A"

julia> names(df, Not(:B)) # селектор, сохраняющий все столбцы, кроме :B
1-element Vector{String}:
 "A"

Чтобы получить имена столбцов в виде Symbol, используйте функцию propertynames:

julia> propertynames(df)
2-element Vector{Symbol}:
 :A
 :B

DataFrames.jl позволяет для удобства использовать Symbol (как :A) и строки (как "A") для всех операций индексирования столбцов. Тем не менее использование Symbol немного быстрее и в целом должно быть предпочтительнее, если не генерировать их с помощью операций со строками.

Создание по столбцам

Также можно начать с пустого DataFrame и добавлять в него столбцы по одному:

julia> df = DataFrame()
0×0 DataFrame

julia> df.A = 1:8
1:8

julia> df[:, :B] = ["M", "F", "F", "M", "F", "M", "M", "F"]
8-element Vector{String}:
 "M"
 "F"
 "F"
 "M"
 "F"
 "M"
 "M"
 "F"

julia> df[!, :C] .= 0
8-element Vector{Int64}:
 0
 0
 0
 0
 0
 0
 0
 0

julia> df
8×3 DataFrame
 Row │ A      B       C
     │ Int64  String  Int64
─────┼──────────────────────
   1 │     1  M           0
   2 │     2  F           0
   3 │     3  F           0
   4 │     4  M           0
   5 │     5  F           0
   6 │     6  M           0
   7 │     7  M           0
   8 │     8  F           0

Построенный таким образом DataFrame имеет 8 строк и 3 столбца. Это можно проверить с помощью функции size:

julia> size(df, 1)
8

julia> size(df, 2)
3

julia> size(df)
(8, 3)

В приведенном выше примере обратите внимание, что выражение df[!, :C] .= 0 создало новый столбец во фрейме данных путем трансляции скаляра.

При задании столбца фрейма данных синтаксисы df[!, :C] и df.C эквивалентны, и они заменят (или создадут) столбец :C в df. Это отличается от использования df[:, :C] для установки столбца во фрейме данных, который обновляет содержимое столбца на месте, если он уже существует.

Вот пример, демонстрирующий это различие. Попробуем изменить столбец :B на двоичную переменную.

julia> df[:, :B] = df.B .== "F"
ERROR: MethodError: Cannot `convert` an object of type Bool to an object of type String

julia> df[:, :B] .= df.B .== "F"
ERROR: MethodError: Cannot `convert` an object of type Bool to an object of type String

Приведенные выше операции не работают, поскольку при использовании : в качестве селектора строк столбец :B обновляется на месте, а он поддерживает только хранение строк.

С другой стороны, работает следующее.

julia> df.B = df.B .== "F"
8-element BitVector:
 0
 1
 1
 0
 1
 0
 0
 1

julia> df
8×3 DataFrame
 Row │ A      B      C
     │ Int64  Bool   Int64
─────┼─────────────────────
   1 │     1  false      0
   2 │     2   true      0
   3 │     3   true      0
   4 │     4  false      0
   5 │     5   true      0
   6 │     6  false      0
   7 │     7  false      0
   8 │     8   true      0

Как видите, поскольку мы использовали df.B в правой части присваивания, столбец :B был заменен. Тот же эффект получился бы в том случае, если бы вместо этого мы использовали df[!, :B] или транслированное присваивание .=.

В разделе Indexing руководства вы найдете подробную информацию обо всех доступных вариантах индексирования.

Построчное создание

DataFrame можно также заполнять построчно. Давайте построим пустой фрейм данных с двумя столбцами (обратите внимание, что первый столбец может содержать только целые числа, а второй — только строки):

julia> df = DataFrame(A=Int[], B=String[])
0×2 DataFrame
 Row │ A      B
     │ Int64  String
─────┴───────────────

Затем можно добавить строки в виде кортежей или векторов, где порядок элементов соответствует порядку столбцов. Чтобы добавить новые строки в конец фрейма данных, используйте push!:

julia> push!(df, (1, "M"))
1×2 DataFrame
 Row │ A      B
     │ Int64  String
─────┼───────────────
   1 │     1  M

julia> push!(df, [2, "N"])
2×2 DataFrame
 Row │ A      B
     │ Int64  String
─────┼───────────────
   1 │     1  M
   2 │     2  N

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

julia> push!(df, Dict(:B => "F", :A => 3))
3×2 DataFrame
 Row │ A      B
     │ Int64  String
─────┼───────────────
   1 │     1  M
   2 │     2  N
   3 │     3  F

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

Чтобы добавить строки в начало фрейма данных, используйте pushfirst!, а чтобы вставить строку в произвольное место, используйте insert!.

С помощью функций append! и prepend! можно также добавлять во фрейм данных целые таблицы.

Построение из другого табличного типа

DataFrames поддерживает интерфейс Tables.jl для взаимодействия с табличными данными. Это означает, что DataFrame можно использовать в качестве «источника» для любого пакета, который ожидает входные данные интерфейса Tables.jl (пакеты формата файлов, пакеты работы с данными и т. д.). DataFrame также может быть приемником для любых входных данных интерфейса Tables.jl. Вот некоторые примеры использования:

df = DataFrame(a=[1, 2, 3], b=[:a, :b, :c])

# запись DataFrame в CSV-файл
CSV.write("dataframe.csv", df)

# хранение DataFrame в таблице базы данных SQLite
SQLite.load!(df, db, "dataframe_table")

# преобразование DataFrame с помощью пакета Query.jl
df = df |> @map({a=_.a + 1, _.b}) |> DataFrame

Частным случаем коллекции, поддерживающей интерфейс Tables.jl, является вектор NamedTuple:

julia> v = [(a=1, b=2), (a=3, b=4)]
2-element Vector{NamedTuple{(:a, :b), Tuple{Int64, Int64}}}:
 (a = 1, b = 2)
 (a = 3, b = 4)

julia> df = DataFrame(v)
2×2 DataFrame
 Row │ a      b
     │ Int64  Int64
─────┼──────────────
   1 │     1      2
   2 │     3      4

Вы можете легко преобразовать фрейм данных обратно в вектор NamedTuple:

julia> using Tables

julia> Tables.rowtable(df)
2-element Vector{NamedTuple{(:a, :b), Tuple{Int64, Int64}}}:
 (a = 1, b = 2)
 (a = 3, b = 4)