Работа с фреймами данных

Изучение данных

Вывод объектов DataFrame по умолчанию включает только выборку строк и столбцов, которая помещается на экране:

julia> using DataFrames

julia> df = DataFrame(A=1:2:1000, B=repeat(1:10, inner=50), C=1:500)
500×3 DataFrame
 Row │ A      B      C
     │ Int64  Int64  Int64
─────┼─────────────────────
   1 │     1      1      1
   2 │     3      1      2
   3 │     5      1      3
   4 │     7      1      4
   5 │     9      1      5
   6 │    11      1      6
   7 │    13      1      7
   8 │    15      1      8
  ⋮  │   ⋮      ⋮      ⋮
 494 │   987     10    494
 495 │   989     10    495
 496 │   991     10    496
 497 │   993     10    497
 498 │   995     10    498
 499 │   997     10    499
 500 │   999     10    500
           485 rows omitted

Параметры вывода можно настроить, вызвав функцию show вручную: show(df, allrows=true) выводит все строки, даже если они не помещаются на экране, а show(df, allcols=true) делает то же самое для столбцов.

Функции first и last можно использовать для просмотра первой и последней строк фрейма данных (соответственно):

julia> first(df, 6)
6×3 DataFrame
 Row │ A      B      C
     │ Int64  Int64  Int64
─────┼─────────────────────
   1 │     1      1      1
   2 │     3      1      2
   3 │     5      1      3
   4 │     7      1      4
   5 │     9      1      5
   6 │    11      1      6

julia> last(df, 6)
6×3 DataFrame
 Row │ A      B      C
     │ Int64  Int64  Int64
─────┼─────────────────────
   1 │   989     10    495
   2 │   991     10    496
   3 │   993     10    497
   4 │   995     10    498
   5 │   997     10    499
   6 │   999     10    500

Также обратите внимание, что при выводе DataFrame на консоль или отрисовке в формате HTML (например, в Jupyter Notebook) вы получаете информацию о типах элементов, содержащихся в его столбцах. Например, здесь

julia> using CategoricalArrays

julia> DataFrame(a=1:2, b=[1.0, missing],
                 c=categorical('a':'b'), d=[1//2, missing])
2×4 DataFrame
 Row │ a      b          c     d
     │ Int64  Float64?   Cat…  Rational…?
─────┼────────────────────────────────────
   1 │     1        1.0  a           1//2
   2 │     2  missing    b        missing

мы видим, что

  • первый столбец :a может содержать элементы типа Int64;

  • второй столбец :b может содержать Float64 или Missing, на что указывает символ ?, отображаемый после имени типа;

  • третий столбец :c может содержать категориальные данные. Здесь мы видим символ , который указывает на то, что реальное имя типа было длинным и было усечено;

  • информация о типе в четвертом столбце :d представляет ситуацию, когда имя является усеченным, а тип допускает Missing.

Получение подмножества

Синтаксис индексирования

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

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

julia> df[1:3, :]
3×3 DataFrame
 Row │ A      B      C
     │ Int64  Int64  Int64
─────┼─────────────────────
   1 │     1      1      1
   2 │     3      1      2
   3 │     5      1      3

julia> df[[1, 5, 10], :]
3×3 DataFrame
 Row │ A      B      C
     │ Int64  Int64  Int64
─────┼─────────────────────
   1 │     1      1      1
   2 │     9      1      5
   3 │    19      1     10

julia> df[:, [:A, :B]]
500×2 DataFrame
 Row │ A      B
     │ Int64  Int64
─────┼──────────────
   1 │     1      1
   2 │     3      1
   3 │     5      1
   4 │     7      1
   5 │     9      1
   6 │    11      1
   7 │    13      1
   8 │    15      1
  ⋮  │   ⋮      ⋮
 494 │   987     10
 495 │   989     10
 496 │   991     10
 497 │   993     10
 498 │   995     10
 499 │   997     10
 500 │   999     10
    485 rows omitted

julia> df[1:3, [:B, :A]]
3×2 DataFrame
 Row │ B      A
     │ Int64  Int64
─────┼──────────────
   1 │     1      1
   2 │     1      3
   3 │     1      5

julia> df[[3, 1], [:C]]
2×1 DataFrame
 Row │ C
     │ Int64
─────┼───────
   1 │     3
   2 │     1

Обратите внимание, что df[!, [:A]] и df[:, [:A]] возвращают объект DataFrame, а df[!, :A] и df[:, :A] — вектор:

julia> df[!, [:A]]
500×1 DataFrame
 Row │ A
     │ Int64
─────┼───────
   1 │     1
   2 │     3
   3 │     5
   4 │     7
   5 │     9
   6 │    11
   7 │    13
   8 │    15
  ⋮  │   ⋮
 494 │   987
 495 │   989
 496 │   991
 497 │   993
 498 │   995
 499 │   997
 500 │   999
485 rows omitted

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

julia> df[!, :A]
500-element Vector{Int64}:
   1
   3
   5
   7
   9
  11
  13
  15
  17
  19
   ⋮
 983
 985
 987
 989
 991
 993
 995
 997
 999

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

В первом случае [:A] является вектором, указывающим, что результирующий объект должен быть фреймом данных (DataFrame). С другой стороны, :A — это одиночный символ, указывающий на то, что должен быть извлечен вектор с одним столбцом. Обратите внимание, что в первом случае требуется передать вектор (а не просто итерируемый объект), поэтому, например, df[:, (:x1, :x2)] не разрешается, а df[:, [:x1, :x2]] допускается.

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

julia> df = DataFrame(x1=1, x2=2, y=3)
1×3 DataFrame
 Row │ x1     x2     y
     │ Int64  Int64  Int64
─────┼─────────────────────
   1 │     1      2      3

julia> df[!, r"x"]
1×2 DataFrame
 Row │ x1     x2
     │ Int64  Int64
─────┼──────────────
   1 │     1      2

Селектор Not (из пакета InvertedIndices) можно применять для выбора всех столбцов, за исключением определенного подмножества:

julia> df[!, Not(:x1)]
1×2 DataFrame
 Row │ x2     y
     │ Int64  Int64
─────┼──────────────
   1 │     2      3

Наконец, можно использовать селекторы Not, Between, Cols и All в более сложных сценариях выбора столбцов (обратите внимание, что Cols() не выбирает ни одного столбца, а All() выбирает все столбцы, поэтому Cols является предпочтительным селектором при написании универсального кода). Ниже приведены примеры использования каждого из этих селекторов:

julia> df = DataFrame(r=1, x1=2, x2=3, y=4)
1×4 DataFrame
 Row │ r      x1     x2     y
     │ Int64  Int64  Int64  Int64
─────┼────────────────────────────
   1 │     1      2      3      4

julia> df[:, Not(:r)] # удаление столбца :r
1×3 DataFrame
 Row │ x1     x2     y
     │ Int64  Int64  Int64
─────┼─────────────────────
   1 │     2      3      4

julia> df[:, Between(:r, :x2)] # сохранение столбцов между :r и :x2
1×3 DataFrame
 Row │ r      x1     x2
     │ Int64  Int64  Int64
─────┼─────────────────────
   1 │     1      2      3

julia> df[:, All()] # сохранение всех столбцов
1×4 DataFrame
 Row │ r      x1     x2     y
     │ Int64  Int64  Int64  Int64
─────┼────────────────────────────
   1 │     1      2      3      4

julia> df[:, Cols(x -> startswith(x, "x"))] # сохранение столбцов, имя которых начинается с "x"
1×2 DataFrame
 Row │ x1     x2
     │ Int64  Int64
─────┼──────────────
   1 │     2      3

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

julia> df[:, Cols(r"x", :)]
1×4 DataFrame
 Row │ x1     x2     r      y
     │ Int64  Int64  Int64  Int64
─────┼────────────────────────────
   1 │     2      3      1      4

julia> df[:, Cols(Not(r"x"), :)]
1×4 DataFrame
 Row │ r      y      x1     x2
     │ Int64  Int64  Int64  Int64
─────┼────────────────────────────
   1 │     1      4      2      3

Синтаксис индексирования также можно использовать для выбора строк на основе условий для переменных:

julia> df = DataFrame(A=1:2:1000, B=repeat(1:10, inner=50), C=1:500)
500×3 DataFrame
 Row │ A      B      C
     │ Int64  Int64  Int64
─────┼─────────────────────
   1 │     1      1      1
   2 │     3      1      2
   3 │     5      1      3
   4 │     7      1      4
   5 │     9      1      5
   6 │    11      1      6
   7 │    13      1      7
   8 │    15      1      8
  ⋮  │   ⋮      ⋮      ⋮
 494 │   987     10    494
 495 │   989     10    495
 496 │   991     10    496
 497 │   993     10    497
 498 │   995     10    498
 499 │   997     10    499
 500 │   999     10    500
           485 rows omitted

julia> df[df.A .> 500, :]
250×3 DataFrame
 Row │ A      B      C
     │ Int64  Int64  Int64
─────┼─────────────────────
   1 │   501      6    251
   2 │   503      6    252
   3 │   505      6    253
   4 │   507      6    254
   5 │   509      6    255
   6 │   511      6    256
   7 │   513      6    257
   8 │   515      6    258
  ⋮  │   ⋮      ⋮      ⋮
 244 │   987     10    494
 245 │   989     10    495
 246 │   991     10    496
 247 │   993     10    497
 248 │   995     10    498
 249 │   997     10    499
 250 │   999     10    500
           235 rows omitted

julia> df[(df.A .> 500) .& (300 .< df.C .< 400), :]
99×3 DataFrame
 Row │ A      B      C
     │ Int64  Int64  Int64
─────┼─────────────────────
   1 │   601      7    301
   2 │   603      7    302
   3 │   605      7    303
   4 │   607      7    304
   5 │   609      7    305
   6 │   611      7    306
   7 │   613      7    307
   8 │   615      7    308
  ⋮  │   ⋮      ⋮      ⋮
  93 │   785      8    393
  94 │   787      8    394
  95 │   789      8    395
  96 │   791      8    396
  97 │   793      8    397
  98 │   795      8    398
  99 │   797      8    399
            84 rows omitted

Если необходимо сопоставить определенное подмножество значений, можно применить функцию in():

julia> df[in.(df.A, Ref([1, 5, 601])), :]
3×3 DataFrame
 Row │ A      B      C
     │ Int64  Int64  Int64
─────┼─────────────────────
   1 │     1      1      1
   2 │     5      1      3
   3 │   601      7    301

Оболочка Ref для [1, 5, 601] необходима для защиты вектора от трансляции (вектор будет рассматриваться как скаляр, если он заключен в Ref). Вы можете написать эту операцию, используя следующее включение (обратите внимание, что она будет медленнее, поэтому не рекомендуется): [a in [1, 5, 601] for a in df.A].

Также функция in может быть вызвана с одним аргументом для создания объекта функции, который проверяет, принадлежит ли каждое значение подмножеству (частичное применение in): df[in([1, 5, 601]).(df.A), :].

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

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

  • когда ! помещается в первую позицию индексирования (df[!, :A] или df[!, [:A, :B]]),

  • когда используется нотация . (getpropery) (df.A),

  • когда одна строка выбирается с помощью целого числа (df[1, [:A, :B]])

  • когда используются view или @view (например, @view df[1:3, :A]).

Более подробную информацию о копиях, видах и ссылках можно найти в разделе [getindex and view](../lib/indexing.md#getindex-and-view).

Функции выделения подмножеств

Альтернативным подходом к выделению подмножества строк во фрейме данных является использование функции subset или функции subset!, которая является ее вариантом на месте.

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

julia> subset(df, :A => a -> a .< 10, :C => c -> isodd.(c))
3×3 DataFrame
 Row │ A      B      C
     │ Int64  Int64  Int64
─────┼─────────────────────
   1 │     1      1      1
   2 │     5      1      3
   3 │     9      1      5

Часто случается, что в столбцах фильтрации могут присутствовать отсутствующие (missing) значения, что может привести к тому, что условие фильтрации вернет missing вместо ожидаемых true или false. Чтобы справиться с этой ситуацией, можно либо использовать функцию coalesce, либо передать функции subset именованный аргумент skipmissing=true. Вот пример:

julia> df = DataFrame(x=[1, 2, missing, 4])
4×1 DataFrame
 Row │ x
     │ Int64?
─────┼─────────
   1 │       1
   2 │       2
   3 │ missing
   4 │       4

julia> subset(df, :x => x -> coalesce.(iseven.(x), false))
2×1 DataFrame
 Row │ x
     │ Int64?
─────┼────────
   1 │      2
   2 │      4

julia> subset(df, :x => x -> iseven.(x), skipmissing=true)
2×1 DataFrame
 Row │ x
     │ Int64?
─────┼────────
   1 │      2
   2 │      4

Функция subset была разработана так, чтобы соответствовать способу задания преобразований столбцов в таких функциях, как combine, select и transform. Примеры преобразований столбцов, принимаемых этими функциями, приведены в следующем разделе.

Кроме того, DataFrames.jl расширяет функции filter и filter!, доступные в Julia Base, которые также позволяют выделять подмножество во фрейме данных. Эти методы определены таким образом, чтобы пакет DataFrames.jl реализовывал API Julia для коллекций, но обычно рекомендуется использовать функции subset и subset!, поскольку они соответствуют другим функциям DataFrames.jl (в отличие от filter и filter!).

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

Вы также можете использовать функции select/select! и transform/transform! для выбора, переименования и преобразования столбцов во фрейме данных.

Функция select создает новый фрейм данных:

julia> df = DataFrame(x1=[1, 2], x2=[3, 4], y=[5, 6])
2×3 DataFrame
 Row │ x1     x2     y
     │ Int64  Int64  Int64
─────┼─────────────────────
   1 │     1      3      5
   2 │     2      4      6

julia> select(df, Not(:x1)) # удаление столбца :x1 в новом фрейме данных
2×2 DataFrame
 Row │ x2     y
     │ Int64  Int64
─────┼──────────────
   1 │     3      5
   2 │     4      6

julia> select(df, r"x") # выбор столбцов, содержащие символ 'x'
2×2 DataFrame
 Row │ x1     x2
     │ Int64  Int64
─────┼──────────────
   1 │     1      3
   2 │     2      4

julia> select(df, :x1 => :a1, :x2 => :a2) # переименование столбцов
2×2 DataFrame
 Row │ a1     a2
     │ Int64  Int64
─────┼──────────────
   1 │     1      3
   2 │     2      4

julia> select(df, :x1, :x2 => (x -> x .- minimum(x)) => :x2) # преобразование столбцов
2×2 DataFrame
 Row │ x1     x2
     │ Int64  Int64
─────┼──────────────
   1 │     1      0
   2 │     2      1

julia> select(df, :x2, :x2 => ByRow(sqrt)) # преобразование столбцов построчно
2×2 DataFrame
 Row │ x2     x2_sqrt
     │ Int64  Float64
─────┼────────────────
   1 │     3  1.73205
   2 │     4  2.0

julia> select(df, :x1, :x2, [:x1, :x2] => ((x1, x2) -> x1 ./ x2) => :z) # преобразование нескольких столбцов
2×3 DataFrame
 Row │ x1     x2     z
     │ Int64  Int64  Float64
─────┼────────────────────────
   1 │     1      3  0.333333
   2 │     2      4  0.5

julia> select(df, :x1, :x2, [:x1, :x2] => ByRow((x1, x2) -> x1 / x2) => :z)  # преобразование нескольких столбцов построчно
2×3 DataFrame
 Row │ x1     x2     z
     │ Int64  Int64  Float64
─────┼────────────────────────
   1 │     1      3  0.333333
   2 │     2      4  0.5

julia> select(df, AsTable(:) => ByRow(extrema) => [:lo, :hi]) # возврат нескольких столбцов
2×2 DataFrame
 Row │ lo     hi
     │ Int64  Int64
─────┼──────────────
   1 │     1      5
   2 │     2      6

Важно отметить, что select всегда возвращает фрейм данных, даже если выбран один столбец (в отличие от синтаксиса индексирования).

julia> select(df, :x1)
2×1 DataFrame
 Row │ x1
     │ Int64
─────┼───────
   1 │     1
   2 │     2

julia> df[:, :x1]
2-element Vector{Int64}:
 1
 2

По умолчанию select копирует столбцы переданного исходного фрейма данных. Чтобы копирование не выполнялось, передайте copycols=false:

julia> df2 = select(df, :x1)
2×1 DataFrame
 Row │ x1
     │ Int64
─────┼───────
   1 │     1
   2 │     2

julia> df2.x1 === df.x1
false

julia> df2 = select(df, :x1, copycols=false)
2×1 DataFrame
 Row │ x1
     │ Int64
─────┼───────
   1 │     1
   2 │     2

julia> df2.x1 === df.x1
true

Чтобы выполнить операцию выбора на месте, используйте select!:

julia> select!(df, Not(:x1));

julia> df
2×2 DataFrame
 Row │ x2     y
     │ Int64  Int64
─────┼──────────────
   1 │     3      5
   2 │     4      6

Функции transform и transform! работают так же, как select и select!, с той лишь разницей, что они сохраняют все столбцы, которые присутствуют в исходном фрейме данных. Вот ряд более сложных примеров.

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

julia> df = DataFrame(x1=[1, 2], x2=[3, 4], y=[5, 6])
2×3 DataFrame
 Row │ x1     x2     y
     │ Int64  Int64  Int64
─────┼─────────────────────
   1 │     1      3      5
   2 │     2      4      6

julia> transform(df, All() => +)
2×4 DataFrame
 Row │ x1     x2     y      x1_x2_y_+
     │ Int64  Int64  Int64  Int64
─────┼────────────────────────────────
   1 │     1      3      5          9
   2 │     2      4      6         12

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

julia> using Random

julia> Random.seed!(1);

julia> df = DataFrame(rand(10, 3), [:a, :b, :c])
10×3 DataFrame
 Row │ a           b          c
     │ Float64     Float64    Float64
─────┼──────────────────────────────────
   1 │ 0.236033    0.555751   0.0769509
   2 │ 0.346517    0.437108   0.640396
   3 │ 0.312707    0.424718   0.873544
   4 │ 0.00790928  0.773223   0.278582
   5 │ 0.488613    0.28119    0.751313
   6 │ 0.210968    0.209472   0.644883
   7 │ 0.951916    0.251379   0.0778264
   8 │ 0.999905    0.0203749  0.848185
   9 │ 0.251662    0.287702   0.0856352
  10 │ 0.986666    0.859512   0.553206

julia> transform(df, AsTable(:) => ByRow(argmax) => :prediction)
10×4 DataFrame
 Row │ a           b          c          prediction
     │ Float64     Float64    Float64    Symbol
─────┼──────────────────────────────────────────────
   1 │ 0.236033    0.555751   0.0769509  b
   2 │ 0.346517    0.437108   0.640396   c
   3 │ 0.312707    0.424718   0.873544   c
   4 │ 0.00790928  0.773223   0.278582   b
   5 │ 0.488613    0.28119    0.751313   c
   6 │ 0.210968    0.209472   0.644883   c
   7 │ 0.951916    0.251379   0.0778264  a
   8 │ 0.999905    0.0203749  0.848185   a
   9 │ 0.251662    0.287702   0.0856352  b
  10 │ 0.986666    0.859512   0.553206   a

В самом сложном примере ниже мы вычисляем сумму построчно, количество элементов и среднее значение, игнорируя пропущенные значения.

julia> using Statistics

julia> df = DataFrame(x=[1, 2, missing], y=[1, missing, missing])
3×2 DataFrame
 Row │ x        y
     │ Int64?   Int64?
─────┼──────────────────
   1 │       1        1
   2 │       2  missing
   3 │ missing  missing

julia> transform(df, AsTable(:) .=>
                     ByRow.([sum∘skipmissing,
                             x -> count(!ismissing, x),
                             mean∘skipmissing]) .=>
                     [:sum, :n, :mean])
3×5 DataFrame
 Row │ x        y        sum    n      mean
     │ Int64?   Int64?   Int64  Int64  Float64
─────┼─────────────────────────────────────────
   1 │       1        1      2      2      1.0
   2 │       2  missing      2      1      2.0
   3 │ missing  missing      0      0    NaN

Хотя пакет DataFrames.jl предоставляет базовые возможности работы с данными, пользователям рекомендуется использовать платформы запросов для выполнения более удобных и мощных операций:

  • пакет Query.jl предоставляет LINQ-подобный интерфейс для большого количества источников данных;

  • пакет DataFramesMeta.jl предоставляет интерфейсы, аналогичные LINQ и dplyr;

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

Дополнительные сведения см. в разделе Data manipulation frameworks.

Обобщение данных

Функция describe возвращает фрейм данных, обобщающий элементарную статистику и информацию о каждом столбце:

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> describe(df)
2×7 DataFrame
 Row │ variable  mean    min  median  max  nmissing  eltype
     │ Symbol    Union…  Any  Union…  Any  Int64     DataType
─────┼────────────────────────────────────────────────────────
   1 │ A         2.5     1    2.5     4           0  Int64
   2 │ B                 F            M           0  String

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

julia> describe(df[!, [:A]])
1×7 DataFrame
 Row │ variable  mean     min    median   max    nmissing  eltype
     │ Symbol    Float64  Int64  Float64  Int64  Int64     DataType
─────┼──────────────────────────────────────────────────────────────
   1 │ A             2.5      1      2.5      4         0  Int64

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

julia> using Statistics

julia> mean(df.A)
2.5

Мы также можем применить функцию к каждому столбцу DataFrame, используя combine. Пример:

julia> df = DataFrame(A=1:4, B=4.0:-1.0:1.0)
4×2 DataFrame
 Row │ A      B
     │ Int64  Float64
─────┼────────────────
   1 │     1      4.0
   2 │     2      3.0
   3 │     3      2.0
   4 │     4      1.0

julia> combine(df, names(df) .=> sum)
1×2 DataFrame
 Row │ A_sum  B_sum
     │ Int64  Float64
─────┼────────────────
   1 │    10     10.0

julia> combine(df, names(df) .=> sum, names(df) .=> prod)
1×4 DataFrame
 Row │ A_sum  B_sum    A_prod  B_prod
     │ Int64  Float64  Int64   Float64
─────┼─────────────────────────────────
   1 │    10     10.0      24     24.0

Если вы хотите, чтобы результат содержал то же количество строк, что и исходный фрейм данных, используйте select вместо combine.

Работа со столбцами, хранящимися в DataFrame

Функции, преобразующие DataFrame для получения нового DataFrame, по умолчанию всегда выполняют копирование столбцов, например:

julia> df = DataFrame(A=1:4, B=4.0:-1.0:1.0)
4×2 DataFrame
 Row │ A      B
     │ Int64  Float64
─────┼────────────────
   1 │     1      4.0
   2 │     2      3.0
   3 │     3      2.0
   4 │     4      1.0

julia> df2 = copy(df);

julia> df2.A === df.A
false

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

julia> x = [3, 1, 2];

julia> df = DataFrame(x=x)
3×1 DataFrame
 Row │ x
     │ Int64
─────┼───────
   1 │     3
   2 │     1
   3 │     2

julia> sort!(df)
3×1 DataFrame
 Row │ x
     │ Int64
─────┼───────
   1 │     1
   2 │     2
   3 │     3

julia> x
3-element Vector{Int64}:
 3
 1
 2

julia> df.x[1] = 100
100

julia> df
3×1 DataFrame
 Row │ x
     │ Int64
─────┼───────
   1 │   100
   2 │     2
   3 │     3

julia> x
3-element Vector{Int64}:
 3
 1
 2

Обратите внимание, что в приведенном выше примере исходный вектор x не изменяется в процессе, поскольку конструктор DataFrame(x=x) по умолчанию создает его копию.

Функции на месте можно без проблем вызывать, за исключением случаев, когда используется представление DataFrame (созданное с помощью view, @view или groupby) или DataFrame, созданный с помощью copycols=false.

Прямой доступ к столбцу col DataFrame df можно получить, используя синтаксисы df.col, df[!, :col], с помощью функции eachcol, обращаясь к parent view столбца DataFrame, или просто сохранив ссылку на вектор столбцов до создания DataFrame с помощью copycols=false.

julia> x = [3, 1, 2];

julia> df = DataFrame(x=x)
3×1 DataFrame
 Row │ x
     │ Int64
─────┼───────
   1 │     3
   2 │     1
   3 │     2

julia> df.x == x
true

julia> df[!, 1] !== x
true

julia> eachcol(df)[1] === df.x
true

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

Точные правила работы со столбцами DataFrame описаны в разделе Принципы работы со столбцами DataFrame руководства.

Замена данных

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

Обратите внимание, что для замены на месте требуется, чтобы заменяемое значение можно было преобразовать в тип элемента столбца. В частности, это означает, что для замены значения на missing требуется вызов allowmissing!, если столбец запрещал отсутствующие значения.

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

julia> using DataFrames

julia> df = DataFrame(a=["a", "None", "b", "None"], b=1:4,
                      c=["None", "j", "k", "h"], d=["x", "y", "None", "z"])
4×4 DataFrame
 Row │ a       b      c       d
     │ String  Int64  String  String
─────┼───────────────────────────────
   1 │ a           1  None    x
   2 │ None        2  j       y
   3 │ b           3  k       None
   4 │ None        4  h       z

julia> replace!(df.a, "None" => "c")
4-element Vector{String}:
 "a"
 "c"
 "b"
 "c"

julia> df
4×4 DataFrame
 Row │ a       b      c       d
     │ String  Int64  String  String
─────┼───────────────────────────────
   1 │ a           1  None    x
   2 │ c           2  j       y
   3 │ b           3  k       None
   4 │ c           4  h       z

Она эквивалентна df.a = replace(df.a, "None" => "c"), но работает на месте без выделения нового вектора столбцов.

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

# замена в подмножестве столбцов [:c, :d]
julia> df[:, [:c, :d]] .= ifelse.(df[!, [:c, :d]] .== "None", "c", df[!, [:c, :d]])
4×2 SubDataFrame
 Row │ c       d
     │ String  String
─────┼────────────────
   1 │ c       x
   2 │ j       y
   3 │ k       c
   4 │ h       z

julia> df
4×4 DataFrame
 Row │ a       b      c       d
     │ String  Int64  String  String
─────┼───────────────────────────────
   1 │ a           1  c       x
   2 │ c           2  j       y
   3 │ b           3  k       c
   4 │ c           4  h       z

julia> df .= ifelse.(df .== "c", "None", df) # замена во всем фрейме данных
4×4 DataFrame
 Row │ a       b      c       d
     │ String  Int64  String  String
─────┼───────────────────────────────
   1 │ a           1  None    x
   2 │ None        2  j       y
   3 │ b           3  k       None
   4 │ None        4  h       z

Обратите внимание, что в приведенных выше примерах замена .= на = приведет к выделению новых векторов столбцов, а не к применению операции на месте.

При замене значений на отсутствующие (missing), если столбцы еще запрещают пропущенные значения, необходимо либо не выполнять операцию на месте и использовать = вместо .=, либо предварительно вызвать allowmissing!:

julia> df2 = ifelse.(df .== "None", missing, df) # не выполнять на месте (`df = ` будет работать)
4×4 DataFrame
 Row │ a        b      c        d
     │ String?  Int64  String?  String?
─────┼──────────────────────────────────
   1 │ a            1  missing  x
   2 │ missing      2  j        y
   3 │ b            3  k        missing
   4 │ missing      4  h        z

julia> allowmissing!(df) # выполнять на месте после разрешения отсутствующих значений
4×4 DataFrame
 Row │ a        b       c        d
     │ String?  Int64?  String?  String?
─────┼───────────────────────────────────
   1 │ a             1  None     x
   2 │ None          2  j        y
   3 │ b             3  k        None
   4 │ None          4  h        z

julia> df .= ifelse.(df .== "None", missing, df)
4×4 DataFrame
 Row │ a        b       c        d
     │ String?  Int64?  String?  String?
─────┼───────────────────────────────────
   1 │ a             1  missing  x
   2 │ missing       2  j        y
   3 │ b             3  k        missing
   4 │ missing       4  h        z