Чем data frame отличается от двумерной матрицы
Перейти к содержимому

Чем data frame отличается от двумерной матрицы

  • автор:

4 Сложные структуры данных в R

Если вдруг вас пугает это слово, то совершенно зря. Матрица (matrix) – это всего лишь “двумерный” вектор: вектор, у которого есть не только длина, но и ширина. Создать матрицу можно с помощью функции matrix() из вектора, указав при этом количество строк и столбцов.

 A  matrix(1:20, nrow = 5, ncol = 4) A 
 [,1] [,2] [,3] [,4] [1,] 1 6 11 16 [2,] 2 7 12 17 [3,] 3 8 13 18 [4,] 4 9 14 19 [5,] 5 10 15 20

Полезное: порядок заполнения матрицы

Заметьте, значения вектора заполняются следующим образом: сначала заполняется первый столбик сверху вниз, потом второй сверху вниз и так до конца, т.е. заполнение значений матрицы идет в первую очередь по вертикали. Это довольно стандартный способ создания матриц, характерный не только для R.

Если мы знаем сколько значений в матрице и сколько мы хотим строк, то количество столбцов указывать необязательно:

 A  matrix(1:20, nrow = 5) A 
 [,1] [,2] [,3] [,4] [1,] 1 6 11 16 [2,] 2 7 12 17 [3,] 3 8 13 18 [4,] 4 9 14 19 [5,] 5 10 15 20

Все остальное так же как и с векторами: внутри находится данные только одного типа. Поскольку матрица – это уже двумерный массив, то у него имеется два индекса. Эти два индекса разделяются запятыми.

 A[2, 3] 
[1] 12

Первый индекс – выбор строк, второй индекс – выбор колонок 1 . Результат – пересечение выбранных строк и столбцов.

Так же как и с векторами, матрицы можно индексировать числовыми векторами:

 A[2:4, 1:3] 
 [,1] [,2] [,3] [1,] 2 7 12 [2,] 3 8 13 [3,] 4 9 14

И даже логическими матрицами (матрицы имеют такие же типы, как и вектора):

 A[A > 10] 
 [1] 11 12 13 14 15 16 17 18 19 20

В этом случае матрица упростится до вектора.

Если же мы оставляем пустое поле вместо числа, то мы выбираем все строки/колонки в зависимости от того, оставили мы поле пустым до или после запятой:

 A[, 1:3] 
 [,1] [,2] [,3] [1,] 1 6 11 [2,] 2 7 12 [3,] 3 8 13 [4,] 4 9 14 [5,] 5 10 15
 [,1] [,2] [,3] [,4] [1,] 2 7 12 17 [2,] 3 8 13 18 [3,] 4 9 14 19
 [,1] [,2] [,3] [,4] [1,] 1 6 11 16 [2,] 2 7 12 17 [3,] 3 8 13 18 [4,] 4 9 14 19 [5,] 5 10 15 20

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

 A[2:4, 2:4]  100 A 
 [,1] [,2] [,3] [,4] [1,] 1 6 11 16 [2,] 2 100 100 100 [3,] 3 100 100 100 [4,] 4 100 100 100 [5,] 5 10 15 20

В принципе, это все, что нам нужно знать о матрицах. Матрицы используются в R довольно редко, особенно по сравнению, например, с MATLAB. Но вот индексировать матрицы хорошо бы уметь: это понадобится в работе с датафреймами (см. Глава 4.4 ).

Для продвинутых: матрица как вектор

То, что матрица – это просто двумерный вектор, не является метафорой: в R матрица – это по сути своей вектор с дополнительными атрибутами dim и (опционально) dimnames . Атрибуты – это свойства объектов, своего рода “метаданные”. Для всех объектов есть обязательные атрибуты типа и длины и могут быть любые необязательные атрибуты. Можно задавать свои атрибуты или удалять уже присвоенные: удаление атрибута dim у матрицы превратит ее в обычный вектор. Про атрибуты подробнее можно почитать здесь или на стр. 99-101 книги “R in a Nutshell” (Adler 2010) .

4.2 Массив

Два измерения – это не предел! Структура с одним типом данных внутри, но с тремя измерениями или больше, называется массивом (array). Создание массива очень похоже на создание матрицы: задаем вектор, из которого будет собран массив, и размерность массива.

 array_3d  array(1:12, c(3, 2, 2)) array_3d 
, , 1 [,1] [,2] [1,] 1 4 [2,] 2 5 [3,] 3 6 , , 2 [,1] [,2] [1,] 7 10 [2,] 8 11 [3,] 9 12

4.3 Список

Теперь представим себе вектор без ограничения на одинаковые данные внутри. И получим список (list)!

 simple_list  list(42, "Пам пам", TRUE) simple_list 
[[1]] [1] 42 [[2]] [1] "Пам пам" [[3]] [1] TRUE

А это значит, что там могут содержаться самые разные данные, в том числе и другие списки, векторы и матрицы (и другие объекты, которые нам еще не знакомы)!

 complex_list  list(c("Wow", "this", "list", "is", "so", "big"), "16", simple_list, A) complex_list 
[[1]] [1] "Wow" "this" "list" "is" "so" "big" [[2]] [1] "16" [[3]] [[3]][[1]] [1] 42 [[3]][[2]] [1] "Пам пам" [[3]][[3]] [1] TRUE [[4]] [,1] [,2] [,3] [,4] [1,] 1 6 11 16 [2,] 2 100 100 100 [3,] 3 100 100 100 [4,] 4 100 100 100 [5,] 5 10 15 20

Если у нас сложный список, то есть очень классная функция str() , чтобы посмотреть, как он устроен:

 str(complex_list) 
List of 4 $ : chr [1:6] "Wow" "this" "list" "is" . $ : chr "16" $ :List of 3 ..$ : num 42 ..$ : chr "Пам пам" ..$ : logi TRUE $ : num [1:5, 1:4] 1 2 3 4 5 6 100 100 100 10 . 

Представьте, что список — это такое дерево с ветвистой структурой. А на конце этих ветвей — листья-векторы.

Как и в случае с векторами мы можем давать имена элементам списка:

 named_list  list(name = "Veronika", age = 26, student = FALSE) named_list 
$name [1] "Veronika" $age [1] 26 $student [1] FALSE

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

 named_list$age 
[1] 26

А вот с индексами сложнее, и в этом очень легко запутаться. Давайте попробуем сделать так, как мы делали это раньше:

 named_list[1] 
$name [1] "Veronika"

Мы, по сути, получили элемент списка – просто как часть списка, т.е. как список длиной один:

 class(named_list) 
[1] "list"
 class(named_list[1]) 
[1] "list"

А вот чтобы добраться до самого элемента списка (и сделать с ним что-то хорошее), нам нужна не одна, а две квадратных скобочки:

 named_list[[1]] 
[1] "Veronika"
 class(named_list[[1]]) 
[1] "character"

Как и в случае с вектором, к элементу списка можно обращаться по имени. Здесь тоже будет иметь значение, одинарные или двойные квадратные скобки вы используете:

 named_list["age"] 
$age [1] 26
 named_list[["age"]] 
[1] 26

Хотя последнее – практически то же самое, что и использование знака $ .

Полезное: зачем нужны списки

Списки довольно часто используются в R, но реже, чем в Python. Со многими объектами в R, такими как результаты статистических тестов, удобно работать именно как со списками – к ним все вышеописанное применимо. Кроме того, некоторые данные мы изначально получаем в виде древообразной структуры – хочешь не хочешь, а придется работать с этим как со списком. Но обычно после этого стоит как можно скорее превратить список в датафрейм.

4.4 Датафрейм

Итак, мы перешли к самому главному. Самому-самому. Датафреймы (dataframes). Более того, сейчас станет понятно, зачем нам нужно было разбираться со всеми предыдущими темами.

Без векторов мы не смогли бы разобраться с матрицами и списками. А без последних мы не сможем понять, что такое датафрейм.

Представьте себе, что мы хотим записать различную информацию о нескольких респондентах. Мы могли бы записать это в список из векторов.

 list(name = c("Veronika", "Eugeny", "Lena", "Misha", "Sasha"), age = c(26, 34, 23, 27, 26), student = c(FALSE, FALSE, TRUE, TRUE, TRUE)) 
$name [1] "Veronika" "Eugeny" "Lena" "Misha" "Sasha" $age [1] 26 34 23 27 26 $student [1] FALSE FALSE TRUE TRUE TRUE

Датафрейм очень похож на список. Просто поменяем в команде выше list() на data.frame() и посмотрим, что изменится:

 df  data.frame(name = c("Veronika", "Eugeny", "Lena", "Misha", "Sasha"), age = c(26, 34, 23, 27, 26), student = c(FALSE, FALSE, TRUE, TRUE, TRUE)) str(df) 
'data.frame': 5 obs. of 3 variables: $ name : chr "Veronika" "Eugeny" "Lena" "Misha" . $ age : num 26 34 23 27 26 $ student: logi FALSE FALSE TRUE TRUE TRUE
 name age student 1 Veronika 26 FALSE 2 Eugeny 34 FALSE 3 Lena 23 TRUE 4 Misha 27 TRUE 5 Sasha 26 TRUE

Вообще, очень похоже на список, не правда ли? Так и есть, датафрейм – это что-то вроде проименованного списка, каждый элемент которого является atomic вектором фиксированной длины. Скорее всего, вы представляли список “горизонтально”. Если это так, то теперь “переверните” список у себя в голове на 90 градусов. Так, чтобы названия векторов оказались сверху, а элементы списка стали столбцами.

Поскольку длина всех этих векторов одинаковая (обязательное условие!), то данные представляют собой табличку, похожую на матрицу. Но в отличие от матрицы, разные столбцы могут иметь разные типы данных. В нашем случае первая колонка – character , вторая колонка – numeric , третья колонка – logical . Тем не менее, обращаться с датафреймом можно и как с проименованным списком, и как с матрицей:

 df$age 
[1] 26 34 23 27 26

Здесь мы сначала извлекли колонку age с помощью оператора $ . Результатом этой операции является числовой вектор. Колонки датафрейма – это и есть векторы!

 df$age[2:3] 
[1] 34 23

Теперь с ним можно работать как с обычным вектором: мы вытащили кусок, выбрав индексы 2 и 3 .

Используя оператор $ и присваивание можно создавать новые колонки датафрейма:

 df$lovesR  TRUE #правило recycling - узнали? согласны? df 
 name age student lovesR 1 Veronika 26 FALSE TRUE 2 Eugeny 34 FALSE TRUE 3 Lena 23 TRUE TRUE 4 Misha 27 TRUE TRUE 5 Sasha 26 TRUE TRUE

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

 df[3:5, 2:3] 
 age student 3 23 TRUE 4 27 TRUE 5 26 TRUE

Как и с матрицами, первый индекс означает строчки, а второй – столбцы.

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

 df[1:2, "age"] 
[1] 26 34
 df[1:2, c("age", "name")] 
 age name 1 26 Veronika 2 34 Eugeny

И здесь перед нами открываются невообразимые возможности! Узнаем, любят ли R те, кто моложе среднего возраста в группе:

 df[df$age  mean(df$age), 4] 
[1] TRUE TRUE TRUE TRUE

Обратите внимание, как удобно нам здесь пригодилось то, что мы научились делать с векторами ( Глава 3 ). Сначала мы посчитали среднее значение абсолютно так же, как мы делали это с векторами:

 mean(df$age) 
[1] 27.2

Полученное среднее поэлементно сравнили с каждым значением колонки (т.е. вектора) df$age :

 df$age  mean(df$age) 
[1] TRUE FALSE TRUE TRUE TRUE

Мы получили логический вектор, длина которого совпадает с длиной датафрейма. При этом TRUE стоит на тех позициях, где в соответствующей строчке в датафрейме возраст респондента больше среднего, а FALSE – в остальных случаях. Теперь этот логический вектор мы используем для выбора строк в исходном датафрейме:

 df[df$age  mean(df$age), ] 
 name age student lovesR 1 Veronika 26 FALSE TRUE 3 Lena 23 TRUE TRUE 4 Misha 27 TRUE TRUE 5 Sasha 26 TRUE TRUE

Наконец, тут же мы можем вытащить нужные колонки, по номеру колонки или ее названию:

 df[df$age  mean(df$age), 4] 
[1] TRUE TRUE TRUE TRUE

Эту же задачу можно выполнить другими способами:

 df$lovesR[df$age  mean(df$age)] 
[1] TRUE TRUE TRUE TRUE
 df[df$age  mean(df$age), 'lovesR'] 
[1] TRUE TRUE TRUE TRUE

В большинстве случаев подходят сразу несколько способов – тем не менее, стоит овладеть ими всеми. Чем богаче ваш арсенал инструментов работы в R, тем легче вам обрабатывать свои данные: возможность сделать одно и то же действие добавляет вам гибкости, потому что разные способы будут более или менее подходящими в разных ситуациях.

Датафреймы удобно просматривать в RStudio. Для это нужно написать команду View(df) или же просто нажать на названии нужной переменной из списка вверху справа (там где Environment). Тогда увидите табличку, очень похожую на Excel и тому подобные программы для работы с таблицами. Там же есть и всякие возможности для фильтрации, сортировки и поиска 2 .

Но, конечно, интереснее все эти вещи делать руками, т.е. с помощью написания кода.

Датафреймы – это структура, которая будет встречаться вам чаще всего при работе с данными в R. С одной стороны, кажется, что она все равно довольно ограниченная: в каждой колонке должно быть одинаковое количество значений, внутри колонки только один тип данных. Но именно так обычно и представлены наши данные. Например, если вы загрузите результаты опроса Google Forms в виде таблицы, то каждая строчка будет респондентом, а каждая колонка – ответом на какой-то вопрос. Поэтому количество значений в каждой колонке будет одинаковым (хотя значения могут быть пропущенными), а каждая колонка – имеет свой тип. Например, год рождения – и это должна быть числовая колонка, с которой вы сможете делать все, что вы умеете делать с числовыми колонками. Например, посчитать возраст. Если в колонке с годом рождения оказалось что-то кроме чисел, то это повод для исследования данных.

4.5 Атрибуты и классы

4.6 Формулы

Формулы – это специальный класс в R, который используется в первую очередь для статистических моделей.

Выглядит формула следующим образом:

 y ~ x 

Вокруг data.table

Эта заметка будет интересна для тех, кто использует библиотеку обработки табличных данных для R — data.table, и, возможно, будет рад увидеть гибкость ее применения на различных примерах.

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

Введение: откуда идет data.table?

Лучше всего начать знакомство с библиотекой немного издалека, а именно, со структур данных, из которых может быть получен объект data.table (далее, ДТ).

Массив

## arrays --------- arrmatr  

Одна из таких структур — это массив (?base::array). Как и в других языках массивы здесь многомерны. Однако интересным является то, что, например, двумерный массив начинает наследовать свойства от класса матрицы (?base::matrix), а одномерный массив, что тоже важно, не наследует от вектора (?base::vector).

При этом надо понимать, что тип данных, содержащихся в каком-либо объекте следует проверять функцией base::typeof, которая возвращает внутреннее описание типа согласно R Internals — общим протоколом языка, связанным с первородным C.

Еще одна команда, для определения класса объекта, base::class, возвращает в случае векторов векторный тип (он отличается названием от внутреннего, но позволяет также понять тип данных).

Список

Из двумерного массива, он же матрица, можно перейти к списку (?base::list).

## lists ------------------ mylist  

При этом происходят несколько вещей сразу:

  • Схлопывается второе измерение матрицы, то есть, мы получаем одновременно и список и вектор.
  • Список, таким образом, наследует от этих классов. Надо иметь в виду, что элементу списка будет соответствовать одно (скалярное) значение из ячейки матрицы-массива.

Датафрейм

От списка, матрицы или вектора можно перейти к датафрейму (?base::data.frame).

## data.frames ------------ df  

Что в нем интересно: датафрейм наследует от списка! Колонки датафрейма являются ячейками списка. Это будет важно в дальнейшем, когда мы будем использовать функции, применяемые к спискам.

data.table

Получить ДТ (?data.table::data.table) можно из датафрейма, списка, вектора или матрицы. Например, вот так (in place).

## data.tables ----------------------- library(data.table) data.table::setDT(df) is.list(df) is.data.frame(df) is.data.table(df) 

Полезно то, что, как и датафрейм, ДТ наследует свойства списка.

ДТ и память

В отличие от всех остальных объектов в R base, ДТ передаются по ссылке. Если нужно сделать копирование в новую область памяти, нужна функция data.table::copy либо нужно сделать выборку из старого объекта.

На этом введение подходит к концу. ДТ — это продолжение развития структур данных в R, которое преимущественно происходит за счет расширения и ускорения операций, производимых над объектами класса датафрейм. При этом сохраняется наследование от других примитивов.

Некоторые примеры использования свойств data.table

Как список.

Итерировать по строкам датафрейма или ДТ не лучшая идея, так как код цикла на языке R гораздно медленее C, а пройтись в цикле по столбцам, которых, обычно, гораздно меньше, вполне можно. Идя по столбцам, помним, что каждый столбец это элемент списка, содержащий, как правило, вектор. А операции над векторами хорошо векторизованы в базовых функциях языка. Также можно использовать операторы выборки, свойственные спискам и векторам: `[[`, `$`.

## operations on data.tables ------------ #using list properties df$'V1'[1] df[['V1']] df[[1]][1] sapply(df, class) sapply(df, function(x) sum(is.na(x))) 

Векторизация

Если есть необходимость пройти по строкам большого ДТ, лучшим решением будет написание функции с векторизацией. Но если это не получается, то следует помнить, что цикл внутри ДТ все равно быстрее цикла в R, так как выполняется на C.

Попробуем на бОльшем примере со 100К строк. Будем вытаскивать первую букву из слов, входящих в вектор-колонку w.

library(magrittr) library(microbenchmark) ## Bigger example ---- rown % .[, d := 1 + b + c + rnorm(nrow(.))] # vectorization microbenchmark(< dt[ , first_l := unlist(strsplit(w, split = ' ', fixed = T))[1] , by = 1:nrow(dt) ] >) # second first_l_f % do.call(rbind, .) %>% `[`(,1) > dt[, first_l := NULL] microbenchmark(< dt[ , first_l := .(first_l_f(w)) ] >) # third first_l_f2 % unlist %>% matrix(nrow = 3) %>% `[`(1,) > dt[, first_l := NULL] microbenchmark(< dt[ , first_l := .(first_l_f2(w)) ] >) 

Первый прогон с итерацией по строкам:

Второй прогон, где векторизация идет через обращение списка в матрицу и взятие элементов на срезе с индексом 1 (последнее и есть собственно векторизация). Поправлюсь: векторизация на уровне функции strsplit, которая умеет принимать вектор на вход. Оказывается, процедура превращения списка в матрицу намного тяжелее самой векторизации, но и в этом случае намного быстрее невекторизованного варианта.

Unit: milliseconds
expr min lq mean median uq max neval
< dt[, `:=`(first_l, .(first_l_f(w)))] >93.07916 112.1381 161.9267 149.6863 185.9893 442.5199 100

Ускорение по медиане в 3 раз.

Третий прогон, где изменена схема превращения в матрицу.

Unit: milliseconds
expr min lq mean median uq max neval
< dt[, `:=`(first_l, .(first_l_f2(w)))] >32.60481 34.13679 40.4544 35.57115 42.11975 222.972 100

Ускорение по медиане в 13 раз.

С этим делом надо экспериментировать, чем больше — тем лучше будет.

Еще один пример с векторизацией, где также текст, но он приближен к реальным условиям: разная длина слов, разное количество слов. Требуется достать первые 3 слова. Вот так:

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

# fourth rown ) , collapse = ' ' ) > ) dt % .[, d := 1 + b + c + rnorm(nrow(.))] first_l_f3 % `[`(n,) %>% as.character > microbenchmark(< dt[ , (paste0('w_', 1:3)) := lapply(1:3, function(x) first_l_f3(w, x)) ] >) dt[ , (paste0('w_', 1:3)) := lapply(1:3, function(x) first_l_f3(w, x)) ] 

Unit: milliseconds
expr min lq mean median

< dt[, `:=`((paste0(«w_», 1:3)), strsplit(w, split = " ", fixed = T))] >851.7623 916.071 1054.5 1035.199
uq max neval
1178.738 1356.816 100

Скрипт отработал со средней скоростью 1 секунда. Неплохо.

Еще один, более экономичный способ, найденный kablag:

# fifth rown ) , collapse = ' ' ) > ) dt % .[, d := 1 + b + c + rnorm(nrow(.))] microbenchmark(< w_split ) 

Медиана 186, дешевле в 5 раз.

Связанные одной цепью.

С объектами ДТ можно работать, используя chaining. Выглядит это как прицепление синтаксиса скобок справа, по сути, сахарок.

# chaining res1  

Течет по трубам.

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

# piping samplpe_b % .[a %in% head(letters)] %>% .[, < dt0 % quantile(seq(0.1, 1, 0.1), na.rm = T) .(q = quants) > , .(cond = b > samplpe_b) ] %>% glm( cond ~ q -1 , family = binomial(link = "logit") , data = . ) %>% summary %>% .[[12]] 

Статистика, машинное обучение и прочее внутри ДТ

Можно использовать лямбда-функции, но иногда лучше создать их отдельно, прописать весь пайплайн анализа данных, и вперед — они работают внутри ДТ. Пример обогащен всеми вышеперечисленными фичами, плюс несколько полезных вещей из арсенала ДТ (таких как обращение к самому ДТ внутри ДТ по ссылке, вставленных иногда не последовательно, но чтобы было).

# function rm(lm_preds) lm_preds else < lmm res > res5 % .[e < 0] %>% .[.[, .I[b > 0]]] %>% .[, `:=` ( low = as.numeric(lm_preds(.SD, .BY, .N)[[1]]) , mean = as.numeric(lm_preds(.SD, .BY, .N)[[2]]) , high = as.numeric(lm_preds(.SD, .BY, .N)[[3]]) , coef_c = as.numeric(lm_preds(.SD, .BY, .N)[[4]][1]) , coef_b = as.numeric(lm_preds(.SD, .BY, .N)[[4]][2]) , coef_int = as.numeric(lm_preds(.SD, .BY, .N)[[4]][3]) ) , a ] %>% .[!is.na(mean), -'e', with = F] # plot plo % ggplot + facet_wrap(~ a) + geom_ribbon( aes( x = c * coef_c + b * coef_b + coef_int , ymin = low , ymax = high , fill = a ) , size = 0.1 , alpha = 0.1 ) + geom_point( aes( x = c * coef_c + b * coef_b + coef_int , y = mean , color = a ) , size = 1 ) + geom_point( aes( x = c * coef_c + b * coef_b + coef_int , y = d ) , size = 1 , color = 'black' ) + theme_minimal() print(plo) 

Заключение

Я надеюсь, что смог создать цельную, но, конечно, не полную, картину такого объекта как data.table, начиная от его свойств связанных с наследованием от классов R и заканчивая его собственными фишками и окружением из элементов tidyverse. Надеюсь, это поможет вам лучше изучить и применять эту библиотеку для работы и развлечения.

Полный код

## load libs ---------------- library(data.table) library(ggplot2) library(magrittr) library(microbenchmark) ## arrays --------- arrmatr % .[, d := 1 + b + c + rnorm(nrow(.))] # vectorization # zero - for loop microbenchmark( < for(i in 1:nrow(dt)) < dt[ i , first_l := unlist(strsplit(w, split = ' ', fixed = T))[1] ] >>) # first microbenchmark(< dt[ , first_l := unlist(strsplit(w, split = ' ', fixed = T))[1] , by = 1:nrow(dt) ] >) # second first_l_f % do.call(rbind, .) %>% `[`(,1) > dt[, first_l := NULL] microbenchmark(< dt[ , first_l := .(first_l_f(w)) ] >) # third first_l_f2 % unlist %>% matrix(nrow = 3) %>% `[`(1,) > dt[, first_l := NULL] microbenchmark(< dt[ , first_l := .(first_l_f2(w)) ] >) # fourth rown ) , collapse = ' ' ) > ) dt % .[, d := 1 + b + c + rnorm(nrow(.))] first_l_f3 % `[`(n,) %>% as.character > microbenchmark(< dt[ , (paste0('w_', 1:3)) := lapply(1:3, function(x) first_l_f3(w, x)) ] >) dt[ , (paste0('w_', 1:3)) := lapply(1:3, function(x) first_l_f3(w, x)) ] # chaining res1 % .[a %in% head(letters)] %>% .[, < dt0 % quantile(seq(0.1, 1, 0.1), na.rm = T) .(q = quants) > , .(cond = b > samplpe_b) ] %>% glm( cond ~ q -1 , family = binomial(link = "logit") , data = . ) %>% summary %>% .[[12]] # function rm(lm_preds) lm_preds else < lmm res > res5 % .[e < 0] %>% .[.[, .I[b > 0]]] %>% .[, `:=` ( low = as.numeric(lm_preds(.SD, .BY, .N)[[1]]) , mean = as.numeric(lm_preds(.SD, .BY, .N)[[2]]) , high = as.numeric(lm_preds(.SD, .BY, .N)[[3]]) , coef_c = as.numeric(lm_preds(.SD, .BY, .N)[[4]][1]) , coef_b = as.numeric(lm_preds(.SD, .BY, .N)[[4]][2]) , coef_int = as.numeric(lm_preds(.SD, .BY, .N)[[4]][3]) ) , a ] %>% .[!is.na(mean), -'e', with = F] # plot plo % ggplot + facet_wrap(~ a) + geom_ribbon( aes( x = c * coef_c + b * coef_b + coef_int , ymin = low , ymax = high , fill = a ) , size = 0.1 , alpha = 0.1 ) + geom_point( aes( x = c * coef_c + b * coef_b + coef_int , y = mean , color = a ) , size = 1 ) + geom_point( aes( x = c * coef_c + b * coef_b + coef_int , y = d ) , size = 1 , color = 'black' ) + theme_minimal() print(plo) 

Введение в R. Разбираемся в анализе данных с использованием статистического пакета

В прош­лой статье из декабрь­ско­го номера я начал говорить об ана­лизе дан­ных и закон­чил на том, как быс­тро решить задачу линей­ной рег­рессии на R. Сегод­ня я более под­робно рас­ска­жу об R как о язы­ке прог­рамми­рова­ния.

Ког­да‑то это был язык для работы со ста­тис­тикой, но сей­час его впол­не мож­но счи­тать язы­ком обще­го наз­начения (хотя основную свою нап­равлен­ность он сох­ранил). Кто не верит, может заг­лянуть на стра­нич­ку про­екта Shiny, с помощью которо­го любой может соз­давать пол­ноцен­ные веб‑при­ложе­ния на R.

Ну да лад­но, нас язык R инте­ресу­ет имен­но в той области, где он дей­стви­тель­но хорош. В этой статье я рас­ска­жу о самых базовых объ­ектах в язы­ке и его осо­бен­ностях. Кто‑то муд­рый дав­но заметил, что самое хорошее в язы­ке R — это то, что он был соз­дан спе­циалис­тами по ста­тис­тике, а самое пло­хое — то, что он был соз­дан спе­циалис­тами по ста­тис­тике :).

Опус­тив воп­росы уста­нов­ки R, с которы­ми доволь­но лег­ко спра­вить­ся (тем более что про это я нем­ного уже писал в прош­лой статье), прис­тупим к изу­чению язы­ка.

Все, что вы хотели знать о функции, но боялись спросить

В язы­ке R дос­тупна очень боль­шая инфраструк­тура пакетов и, как следс­твие, совер­шенно неверо­ятное количес­тво фун­кций для пов­седнев­ного исполь­зования. Что делать, если ты зна­ешь наз­вание, но не пом­нишь пра­виль­ное упот­ребле­ние фун­кции? Решени­ем ста­нет очень при­лич­ный help, пра­виль­ным обра­зом встро­енный в сис­тему. Для того что­бы получить справ­ку об исполь­зовании той или иной фун­кции, дос­таточ­но наб­рать ?имя_функции .

Векторы

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

В язы­ке R пол­но сюр­при­зов, и пер­вый из них зак­люча­ется в том, что при­митив­ным объ­ектом в R явля­ется век­тор, то есть совокуп­ность зна­чений одной при­роды. К при­меру, век­тор вещес­твен­ных чисел. Хочет­ся спро­сить, а как быть с обыч­ными чис­лами? Ска­жем, с чис­лом 10. Ответ на этот воп­рос доволь­но прост — это век­тор из одно­го эле­мен­та.

Век­торы быва­ют сле­дующих типов:

  • це­лые;
  • чис­ловые (вещес­твен­ные);
  • сим­воль­ные;
  • ком­плексные;
  • ло­гичес­кие.

По умол­чанию чис­ла в R — это вещес­твен­ные чис­ла, то есть чис­ла с пла­вающей запятой. Для того что­бы явно ука­зать R, что чис­ло, с которым ты собира­ешь­ся иметь дела, целое, нуж­но добавить суф­фикс L. Нап­ример, 10L. Это лег­ко иллюс­три­рует сле­дующий код:

[ 1 ] "integer"

Здесь будет исполь­зовать­ся сим­вол приг­лашения > , что­бы отли­чать код от отве­та сис­темы. В работе с чис­лами сущес­тву­ет спе­циаль­ный сим­вол Inf для пред­став­ления бес­конеч­ности.

Сле­дует обра­тить вни­мание на опе­ратор прис­ваива­ния < - . Рас­смот­рим сле­дующий фраг­мент:

> x # печатаем x
> print ( x ) # еще раз печатаем

В этом, казалось бы, оче­вид­ном фраг­менте кода есть два важ­ных момен­та: печатать мож­но, прос­то ука­зывая имя перемен­ной в стро­ке или исполь­зуя фун­кцию, пред­назна­чен­ную для печати. Это доволь­но харак­терно для всех язы­ков, в которых есть инте­рак­тивный интер­пре­татор REPL (Read — Evaluate — Print Loop). Фун­кция print ско­рее исполь­зует­ся для печати внут­ри дру­гих фун­кций для отладки или прос­то для вывода какой‑либо информа­ции. Что более важ­но и менее оче­вид­но, стро­ка [ 1] 10 , выводи­мая в качес­тве резуль­тата в R, говорит, что это пер­вый (и единс­твен­ный) эле­мент век­тора.

Чис­ло 1 в квад­ратных скоб­ках выводит­ся для удобс­тва чте­ния. К при­меру, если век­тор не вле­зает в ширину экра­на, то он раз­бива­ется на стро­ки и чис­ла перед каж­дой стро­кой — это индекс эле­мен­та век­тора, с которо­го начина­ется дан­ная стро­ка.

Для соз­дания обыч­ного век­тора исполь­зует­ся фун­кция c :

> x < - c ( 1 , 2 , 3 )

Так же как и во мно­гих дру­гих язы­ках, мож­но соз­давать век­торы, ука­зывая интервал зна­чений:

> x < - c ( 1 : 3 )

Ка­залось бы, резуль­тат тот же, одна­ко все не сов­сем так. Так как это целочис­ленный интервал, содер­жимое вто­рого век­тора — это целые чис­ла, а пер­вого — вещес­твен­ные. Что лег­ко про­верить с помощью фун­кции typeof . В пос­леднем слу­чае еще мож­но писать прос­то x < - 1: 3 . Зна­чения булева типа в язы­ке R выг­лядят как TRUE и FALSE или прос­то T и F . Для того что­бы соз­дать пус­той век­тор нуж­ного типа, необ­ходимо исполь­зовать фун­кцию vector .

> x < - vector ( "numeric" , length = 10 ) [ 1 ] 0 0 0 0 0 0 0 0 0 0

Не­явное пре­обра­зова­ние типов в R хорошо иллюс­три­рует­ся сле­дующим при­мером:

> x < - c ( "a" , TRUE , 1. 3 ) [ 1 ] "a" "TRUE" "1. 3" > y < - c ( 2 , TRUE , FALSE )

Час­то быва­ет необ­ходимо вос­поль­зовать­ся явным пре­обра­зова­нием типов. Рас­смот­рим при­мер:

> as ( TRUE , "character" )
> as. character ( TRUE )

Два строч­ки дела­ют в точ­ности то же самое, одна­ко с точ­ки зре­ния чита­емос­ти кода
вто­рой под­ход выг­лядит более пред­почти­тель­ным. Вооб­ще, фун­кции соз­дания, про­вер­ки и пре­обра­зова­ния типов лег­ко запом­нить сле­дующим обра­зом. Для соз­дания (пус­того век­тора строк) исполь­зует­ся character( length=5) , где length — количес­тво эле­мен­тов, is. character исполь­зует­ся для срав­нения, а as. character для пре­обра­зова­ния. Ког­да пре­обра­зова­ние невоз­можно, то его резуль­татом будет спе­циаль­ное зна­чение NA .

Эле­мен­ты век­тора могут быть заиме­нова­ны, это мож­но сде­лать сле­дующим обра­зом:

> v < - c ( x = 1. 0 , y = 2. 5 , z = -0. 1 ) 1. 0 2. 5 -0. 1 > u < - c ( 1. 0 , -0. 5 , -0. 5 ) > names ( u ) < - c ( "x" , "y" , "z" )

Матрицы

С век­торами все доволь­но прос­то, давай теперь поп­робу­ем разоб­рать­ся с дру­гой полез­ной струк­турой дан­ных — мат­рицами. Для соз­дания мат­рицы есть спе­циаль­ная фун­кция matrix :

> m < - matrix ( nrow = 2 , ncol = 3 ) [ , 1 ] [ , 2 ] [ , 3 ] [ 1 , ] NA NA NA [ 2 , ] NA NA NA

Как вид­но, изна­чаль­но соз­дает­ся пус­тая мат­рица. Для того что­бы получить раз­меры мат­рицы, сущес­тву­ет спе­циаль­ный атри­бут dim :

> attributes ( m )

Сле­дует отме­тить, что в смыс­ле хра­нения дву­мер­ных объ­ектов (мас­сивов, мат­риц) все язы­ки делят­ся на две груп­пы: те, что хра­нят мат­рицы по стро­кам, такие как C и Java, и те, что хра­нят по стол­бцам, — это, к при­меру, FORTRAN и R. В том, что это имен­но так, лег­ко убе­дить­ся сле­дующим обра­зом:

> m < - matrix ( 1 : 6 , nrow = 2 , ncol = 3 ) [ , 1 ] [ , 2 ] [ , 3 ]

При­чем задавать дву­мер­ную струк­туру мож­но, прос­то добав­ляя атри­бут dim к век­тору:

> dim ( v ) < - c ( 2 , 3 ) [ , 1 ] [ , 2 ] [ , 3 ]

Сверх того, стро­кам и колон­кам мат­рицы мож­но давать име­на:

> m < - matrix ( 1 : 4 , nrow = 2 , ncol = 2 ) > dimnames ( m ) < - list ( c ( "a" , "b" ) , c ( "c" , "d" ) )

В язы­ке R сущес­тву­ет так­же механизм соз­дания дву­мер­ных струк­тур из одно­мер­ных с помощью опе­раций при­соеди­нения стро­ки или стол­бца:

> y < - 11 : 13 > cbind ( x , y )
> rbind ( x , y )
[ , 1 ] [ , 2 ] [ , 3 ]
y 11 12 13

О том, как работать с отдель­ными эле­мен­тами струк­тур дан­ных, я напишу чуть поз­же, а пока про­дол­жим раз­бирать­ся с самими струк­турами.

Списки и факторы

В этом раз­деле рас­смот­рим еще две полез­ные струк­туры дан­ных. Пер­вая — это, конеч­но, спи­сок. Дело в том, что час­то при­ходит­ся хра­нить дан­ные раз­ного типа в одном мес­те.

Как мы зна­ем, век­тор здесь не под­ходит, потому что его эле­мен­ты дол­жны быть одно­го типа, поэто­му в R пре­дус­мотре­ны спис­ки:

> lst < - list ( "hello" , 1. 5 , TRUE , 1+2i )

Как вид­но, в спис­ке содер­жится четыре эле­мен­та, и все они раз­ного типа: стро­ка, вещес­твен­ное чис­ло, булево зна­чение и ком­плексное чис­ло. Эле­мен­ты спис­ка мож­но име­новать, как и эле­мен­ты век­тора:

> l < - list ( a = "test" , b = 3. 14 )

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

> x < - factor ( c ( "yes" , "no" , "yes" , "no" , "no" ) ) [ 1 ] yes no yes no no Levels : no yes

Здесь соз­дает­ся век­тор фак­торов с дву­мя воз­можны­ми зна­чени­ями: yes и no. Мож­но, к при­меру, под­счи­тать, сколь­ко соот­ветс­тву­ющих зна­чений есть в нашем век­торе:

Фрейм данных (Data Frame)

Фрейм дан­ных — один из самых полез­ных типов дан­ных в R. Ког­да мы работа­ем с реаль­ными таб­личны­ми дан­ными, имен­но этот тип пред­став­ляет таб­лицы. В отли­чие от мат­риц, дан­ный тип поз­воля­ет хра­нить раз­личные типы дан­ных в раз­ных колон­ках. С точ­ки зре­ния хра­нения этот тип дан­ных мож­но пред­ста­вить как спи­сок спе­циаль­ного вида, где эле­мен­тами спис­ка явля­ются спис­ки оди­нако­вой дли­ны (колон­ки). Для заг­рузки фрей­ма дан­ных из CSV-фай­ла сущес­тву­ет фун­кция read. csv , которая уже встре­чалась нам в пре­дыду­щей статье этой серии.

Мож­но соз­дать фрейм дан­ных вруч­ную, нап­ример так:

> x < - data. frame ( a = c ( F , F , T , T ) , b = c ( F , T , F , T ) , or = c ( F , T , T , T ) ) 1 FALSE FALSE FALSE 2 FALSE TRUE TRUE 3 TRUE FALSE TRUE 4 TRUE TRUE TRUE

По­мимо атри­бута names , для фрей­ма дан­ных так­же есть row. names :

> row. names ( x )
[ 1 ] "1" "2" "3" "4"

Доступ к элементам

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

> x < - c ( 11 , 21 , 31 , 41 , 11 , 21 , 31 ) [ 1 ] 31 41 31 [ 1 ] FALSE FALSE TRUE TRUE FALSE FALSE TRUE [ 1 ] 31 41 31 [ 1 ] 11 21 31

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

При работе с мат­рицами так­же не воз­ника­ет никаких слож­ностей, нуж­ные стро­ки и стол­бцы мы можем получить лег­ко и неп­ринуж­денно. К при­меру, запись x[ 2, 3] — это эле­мент во вто­рой стро­ке и в треть­ем стол­бце, а x[ 1, ] и z[, 2] — это пер­вая стро­ка и вто­рой стол­бец соот­ветс­твен­но. По умол­чанию эти опе­рации воз­вра­щают век­тор, а не мат­рицу, в которой одна стро­ка или стол­бец, и если мы хотим, что­бы резуль­татом выпол­нения дан­ной опе­рации была все‑таки мат­рица, пусть и дру­гого раз­мера, то нуж­но исполь­зовать допол­нитель­ный параметр x[ 1, , drop=FALSE] .

С дос­тупом к эле­мен­там спис­ка все нем­ного слож­нее. Рас­смот­рим сле­дующий при­мер:

> l < - list ( a = 0. 5 , b = 1 : 3 ) [ 1 ] "integer"

На этом при­мере дос­таточ­но хорошо вид­ны осо­бен­ности дос­тупа к эле­мен­там в R.

Как мож­но заметить, исполь­зование [[] ] не гаран­тиру­ет соот­ветс­твие типа воз­вра­щаемо­го зна­чения изна­чаль­ному, а в слу­чае оди­нар­ных ско­бок [ ] воз­вра­щаемое зна­чение так­же явля­ется спис­ком.

В этом смыс­ле $ и [[] ] работа­ют очень похоже. Хотя есть некото­рая осо­бен­ность — зна­чение в двой­ных квад­ратных скоб­ках может быть вычис­лено, а имя пос­ле зна­ка $ — нет.

Спис­ки быва­ют вло­жен­ными, и дос­туп к их эле­мен­там осу­щест­вля­ется с помощью вло­жен­ных ско­бок, как и полага­ется: x[[ 1]][[ 3] ] , одна­ко мож­но сде­лать запись чуть более понят­ной, исполь­зуя фун­кцию c . К при­меру, пос­леднее выраже­ние мож­но записать как x[[ c( 1, 3)] ] . При­чем исполь­зовать селек­тор с одной скоб­кой не получит­ся (подумай почему).

Управляющие структуры

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

Нач­нем с условно­го опе­рато­ра. Надо ска­зать, что здесь в R нет поч­ти ничего осо­бен­ного, но все‑таки:

if ( x > 0 ) < y < - x >else < y < - - x >

Ни­чего нового тут нет, и else может быть опу­щен. Хотя и тут не обош­лось без нес­коль­ко необыч­ного поведе­ния. В фун­кци­ональ­ных язы­ках, таких как Haskell, конс­трук­ция if явля­ется выраже­нием, а не опе­рато­ром, то есть воз­вра­щает зна­чение, а не изме­няет сос­тояние. В язы­ке R эта идея так­же наш­ла себе мес­то в сле­дующей конс­трук­ции:

y < - if ( x >0 ) < x >else < - x ># фигурные скобки можно опустить

Пос­ледняя конс­трук­ция дела­ет то же самое, что и пре­дыду­щая, толь­ко в фун­кци­ональ­ном сти­ле. Одна­ко в фун­кци­ональ­ных язы­ках выраже­ние дол­жно быть опре­деле­но и поэто­му наличие вет­ви else обя­затель­но. Здесь же это не так, конс­трук­ция z < - if ( x < 0) -x впол­не допус­тима, но зна­чени­ем это­го выраже­ния при x >0 будет спе­циаль­ное зна­чение NULL . Для срав­нения с этим зна­чени­ем мож­но исполь­зовать фун­кцию is. null .

Те­перь сто­ит ска­зать пару слов о цик­лах. Цик­лы в R работа­ют мед­ленно, но ког­да мы име­ем срав­нитель­но неболь­шой объ­ем дан­ных, то исполь­зование цик­лов может быть впол­не допус­тимо и даже удоб­но. С этой точ­ки зре­ния R так­же мало отли­чает­ся от дру­гих язы­ков прог­рамми­рова­ния. Нач­нем с цик­ла for , который реали­зует извес­тную парадиг­му for-in.

x < - c ( "a" , "b" , "c" , "d" , "e" ) for ( i in 1 : 5 ) < print ( x [ i ] ) for ( ch in x ) print ( ch )

Как и полага­ется, цикл for-in реали­зует про­цесс ите­рации по век­тору или пос­ледова­тель­нос­ти, пос­ледний вари­ант более лакони­чен, одна­ко если нам каким‑либо обра­зом нуж­ны индексы, то хорошо бы иметь воз­можность соз­давать пос­ледова­тель­ность, соот­ветс­тву­ющую задан­ному век­тору.

Для это­го в R пре­дус­мотре­на спе­циаль­ная фун­кция seq_along , при­нима­ющая в качес­тве аргу­мен­та век­тор или спи­сок, для которых стро­ится пос­ледова­тель­ность индексов. Таким обра­зом, пер­вый цикл мож­но было бы перепи­сать в виде for( i in seq_along( x)) < . >. Для того что­бы сге­нери­ровать пос­ледова­тель­ность задан­ной дли­ны, мож­но вос­поль­зовать­ся фун­кци­ей seq_len .

Кста­ти, все эти воп­росы лег­ко решить извес­тны­ми средс­тва­ми, исполь­зуя лишь фун­кцию вычис­ления дли­ны length . Цикл while име­ет впол­не клас­сичес­кую фор­му while ( cond) < . >.

Ло­гичес­кие связ­ки в R так­же выг­лядят стан­дар­тным обра­зом: && , || и ! . В допол­нение к баналь­ному while( TRUE) в R при­сутс­тву­ет небаналь­ный repeat < . >, выход из которо­го обес­печива­ет, как обыч­но, ком­бинация if и break . Для перехо­да к сле­дующей ите­рации пре­дус­мотрен опе­ратор с нес­коль­ко неожи­дан­ным наз­вани­ем next .

Как мож­но заметить, раз­делите­лей вро­де точ­ки с запятой меж­ду опе­рато­рами в R нет.

Функции

Как уж без фун­кций в при­лич­ном язы­ке прог­рамми­рова­ния? В самом общем виде опре­деле­ние фун­кции выг­лядит сле­дующим обра­зом:

Как и в фун­кци­ональ­ных язы­ках, фун­кции в R явля­ются объ­екта­ми клас­са. Это озна­чает, что их мож­но передать в качес­тве аргу­мен­та в дру­гую фун­кцию и вер­нуть в качес­тве зна­чения. Ано­ним­ные (лям­бда) фун­кции так­же при­сутс­тву­ют:

f < - function ( g ) < function ( x ) g ( g ( x ) ) y < - f ( function ( x ) x * x ) ( 5 )

Здесь фун­кция f при­нима­ет в качес­тве аргу­мен­та фун­кцию g и воз­вра­щает фун­кцию, которая име­ет один фор­маль­ный параметр x и дваж­ды при­меня­ет к нему фун­кцию g . Так­же в коде мож­но уви­деть переда­чу ано­ним­ной фун­кции в качес­тве аргу­мен­та, а получен­ный резуль­тат (ком­позиция фун­кций g и самой себя) при­меня­ется к чис­лу 5. Таким обра­зом, чис­ло 5 будет дваж­ды воз­ведено в квад­рат.

По­рядок вычис­ления аргу­мен­тов в R явля­ется отло­жен­ным (lazy), то есть аргу­мент не вычис­ляет­ся, если он не нужен:

> f < - function ( x , y ) x * x > f ( 3 , 5 / 0 )

Для того что­бы пра­виль­но работать с сос­тоянием в слу­чае замыка­ний, сущес­тву­ет опе­ратор

Как создать DataFrame в Pandas?

Хотите создать DataFrame, но не знаете как? Мы поделимся с Вами сразу несколькими ваиантами! Однако, не бывает практики без теории, поэтому сначала — небольшой ликбез о том, что такое DataFrame и какое данные он может содержать.

Краткий ликбез о DataFrame

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

Как создать DataFrame?

Если Вы работаете с большими данными, то, вероятнее всего, Вам чаще всего приходится иметь дело с табличными данными, в которых строки пронумерованы. Давайте посмотрим, как будет выглядеть DataFrame с пронумерованными строками и именованными столбцами:

Как же создать DataFrame ?

Существует несколько вариантов создания DataFrame. Но какой бы вариант мы ни выбрали, в основе него будет лежать конструкция:

pandas.DataFrame(data, [columns, index, type, copy])

Параметры, которые мы будем использовать:

  • data — (обязательный параметр) это данные, из которых планируется создать DataFrame
  • columns — (необязательный параметр) это список с именами столбцов
  • index — (необязательный параметр) это список с именами столбцов

Если параметры columns или index не определены, то столбцы и строки будут пронумерованы автоматически целыми числовыми значениями, начиная с нуля.

Узнать больше информации о параметрах pandas.DataFrame() можно из официальной документации по ссылке: https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.html

Рассмотрим несколько наиболее практичных, на мой взгляд, вариантов создания DataFrame:

  1. Создание DataFrame из списка списков
  2. Создаем DataFrame из списка словарей
  3. Создаем DataFrame из numpy-массива
  4. Создание DataFrame из pandas.Series

Создание DataFrame из списка списков

Сначала создадим список из списков, с учетом того, что каждый вложенный список — это будущий ряд DataFrame. После этого создадим DataFrame по имени df , воспользовавшись конструкцией pd.DataFrame().

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

import pandas as pd # Создать список из списков list_of_lists = [ [3, 4, 'отл.'], [5, 4, 'хор.'], [4, 5, 'отл.'], ] # Создаем DataFrame # Удалите параметр index, если вместо имен строк нужно выводить индексы: df = pd.DataFrame(list_of_lists, columns = ['Матем.', 'Лит-ра', 'Физ-ра'], index = ['Ворчун', 'Тихоня', 'Добряк'])

В результате получим DataFrame «df»:

Создаем DataFrame из списка словарей

Сначала создадим список словарей с учетом того, что каждый словарь представляет собой будущую строку DataFrame. Ключами словарей являются имена столбцов DataFrame, поэтому параметр columns в pd.DataFrame() определять не нужно. Параметр index определяется, если нужно получить DataFrame с именованными строками:

import pandas as pd # Создаем список словарей list_of_dict = [ , , ] # Удалите параметр index, если вместо имен строк нужно выводить индексы: df = pd.DataFrame(list_of_dict, index = ['Ворчун', 'Тихоня', 'Добряк'])

Полученный DataFrame df:

Как создать DataFrame из numpy-массива?

Сначала создадим numpy-массив arr так, чтобы строки массива включали в себя данные будущих столбцов DataFrame. После этого сформируем DataFrame из транспонированного массива arr.T, при необходимости зададим имена строк и столбцов в параметрах columns и index:

import pandas as pd import numpy as np arr = np.array([ [3, 5, 4], [4, 4, 5], ['отл.', 'хор.', 'отл.'] ]) # Удалите параметр index, если не нужны имена строк: df = pd.DataFrame(arr.T, columns = ['Матем.', 'Лит-ра', 'Физ-ра'], index = ['Ворчун', 'Тихоня', 'Добряк'])

Полученный DataFrame df:

Создание DataFrame из pandas.Series

Сначала создадим одномерные структуры данных — pandas-серии, содержащие значения из будущих столбцов DataFrame. После этого из pandas-серии сформируем DataFrame df, передав при необходимости имена строк и столбцов в параметрах columns и index:

import pandas as pd math = pd.Series([3, 5, 4]) litra = pd.Series([4, 4, 5]) fizra = pd.Series(['отл', 'хор', 'отл']) df = pd.DataFrame((math, litra, fizra), columns = ['Матем.', 'Лит-ра', 'Физ-ра'], index = ['Ворчун', 'Тихоня', 'Добряк' ])

В результате выполнения кода получим DataFrame df:

Используете для работы Jupiter Notebook? Тогда Вам точно будет интресно, как сменить тему в этом редакторе: «Как сменить тему в Jupyter Notebook?».

Вебторт рекомендует! ��

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *