Питання Як зробити відмінний R приклад, який можна відтворити?


Обговорюючи роботу з колегами, навчаючи, надсилаючи звіт про помилку або шукаючи поради щодо списків розсилки, а також на SO, а відтворюваний приклад часто запитують і завжди корисні.

Які ваші поради для створення чудового прикладу? Як ви вставляєте структури даних з  в текстовому форматі? Яку іншу інформацію слід включити?

Є додаткові трюки, крім використання dput(), dump() або structure()? Коли слід включити library() або require() заяви Які зарезервовані слова слід уникати, крім c, df, dataі т. д.?

Як зробити чудову  відтворюваний приклад?


2380


походження


Я заплутався з приводу сфери питання. Люди, схоже, стрибнули інтерпретацію відтворюваного прикладу, задаючи питання про SO або R-help (як "відтворити помилку"). А що стосується відтворюваних прикладів R на довідкових сторінках? У пакетних демонстраціях? У навчальних посібниках / презентаціях? - baptiste
@ baptiste: те ж саме мінус помилка. Всі методи, які я пояснив, використовуються в довідкових сторінках пакету, а також у навчальних посібниках та презентаціях, присвячених мені - Joris Meys
Дані іноді є граничним чинником, оскільки структура може бути надто складною для імітації. Для отримання публічних даних з приватних даних: stackoverflow.com/a/10458688/742447 в stackoverflow.com/questions/10454973/... - Etienne Low-Décarie


Відповіді:


Мінімальний відтворений приклад складається з наступних елементів:

  • мінімальний набір даних, необхідний для відтворення помилки
  • мінімальний руйнівний код, необхідний для відтворення помилки, яку можна запустити на даному наборі даних.
  • необхідну інформацію про використані пакунки, версію R та систему, на якій вона запускається.
  • у випадку випадкових процесів, насіння (встановлено на set.seed()) для відтворюваності

Перегляд корисних прикладів файлів довідки використаних функцій часто є корисним. Взагалі, весь наданий код відповідає вимогам мінімального відтворюваного прикладу: дані надаються, надано мінімальний код, і все є незмінним.

Виготовлення мінімального набору даних

У більшості випадків це легко зробити, просто надаючи векторний / кадр даних з деякими значеннями. Або ви можете скористатись одним із вбудованих наборів даних, які забезпечені більшістю пакетів.
Ви можете побачити повний список вбудованих наборів даних library(help = "datasets"). Кожний набір даних є коротким описом, і, наприклад, можна отримати додаткову інформацію ?mtcars де "mtcars" є одним з наборів даних у списку. Інші пакети можуть містити додаткові набори даних.

Створення вектора легко. Іноді необхідно додати до неї якусь випадковість, і для цього є цілий ряд функцій. sample() може рандомізувати вектор або дати випадковий вектор з лише кількома значеннями. letters є корисним вектором, що містить алфавіт. Це можна використовувати для створення факторів.

Кілька прикладів:

  • випадкові величини: x <- rnorm(10) для нормального розподілу x <- runif(10) для рівномірного розподілу, ...
  • перестановка деяких значень: x <- sample(1:10) для вектора 1:10 у випадковому порядку.
  • випадковий фактор: x <- sample(letters[1:4], 20, replace = TRUE)

Для матриць можна використовувати matrix(), наприклад:

matrix(1:10, ncol = 2)

Створення кадрів даних можна зробити за допомогою data.frame(). Потрібно звернути увагу на назви записів у кадрі даних, щоб зробити їх не надто складними.

Приклад :

set.seed(1)
Data <- data.frame(
    X = sample(1:10),
    Y = sample(c("yes", "no"), 10, replace = TRUE)
)

Для деяких питань може знадобитися певний формат. Для них можна використовувати будь-яку з наданих as.someType функції: as.factor, as.Date, as.xts, ... Це у поєднанні з вектором та / або трюками кадру даних.

Скопіюйте свої дані

Якщо у вас є деякі дані, які можуть бути надто складними для побудови з використанням цих порад, то завжди можна скласти підмножина вихідних даних, використовуючи наприклад head(), subset()або індекси. Тоді використовуйте наприклад. dput() щоб дати нам те, що можна вставити в R негайно:

> dput(head(iris,4))
structure(list(Sepal.Length = c(5.1, 4.9, 4.7, 4.6), Sepal.Width = c(3.5, 
3, 3.2, 3.1), Petal.Length = c(1.4, 1.4, 1.3, 1.5), Petal.Width = c(0.2, 
0.2, 0.2, 0.2), Species = structure(c(1L, 1L, 1L, 1L), .Label = c("setosa", 
"versicolor", "virginica"), class = "factor")), .Names = c("Sepal.Length", 
"Sepal.Width", "Petal.Length", "Petal.Width", "Species"), row.names = c(NA, 
4L), class = "data.frame")

Якщо ваш кадр даних має фактор з багатьма рівнями, то dput висновок може бути громіздким, оскільки він все одно буде перераховувати всі можливі рівні факторів, навіть якщо вони відсутні в підмножині ваших даних. Щоб вирішити цю проблему, ви можете скористатись droplevels() функція Зверніть увагу нижче, як вид є фактором, що має лише один рівень:

> dput(droplevels(head(iris, 4)))
structure(list(Sepal.Length = c(5.1, 4.9, 4.7, 4.6), Sepal.Width = c(3.5, 
3, 3.2, 3.1), Petal.Length = c(1.4, 1.4, 1.3, 1.5), Petal.Width = c(0.2, 
0.2, 0.2, 0.2), Species = structure(c(1L, 1L, 1L, 1L), .Label = "setosa",
class = "factor")), .Names = c("Sepal.Length", "Sepal.Width", 
"Petal.Length", "Petal.Width", "Species"), row.names = c(NA, 
4L), class = "data.frame")

Ще одне застереження для dput це те, що він не буде працювати для клавіш data.table об'єктів або для групування tbl_df (клас grouped_df) від dplyr. У цих випадках ви можете повернутися до звичайного кадру даних перед тим, як поділитися, dput(as.data.frame(my_data)).

Найгіршим сценарієм ви можете надати текстове представлення, яке можна читати, використовуючи text параметр read.table :

zz <- "Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa
4          4.6         3.1          1.5         0.2  setosa
5          5.0         3.6          1.4         0.2  setosa
6          5.4         3.9          1.7         0.4  setosa"

Data <- read.table(text=zz, header = TRUE)

Виготовлення мінімального коду

Це повинно бути простою частиною, але часто це не так. Те, що ви не повинні робити, це:

  • додати всі види перетворень даних. Переконайтеся, що надані дані вже в правильному форматі (якщо це звичайно не є проблемою).
  • скопіюйте-вставте цілу функцію / кусок коду, що дає помилку. По-перше, спробуйте визначити, які рядки точно призводять до помилки. Найчастіше ви дізнаєтеся, яка проблема є для вас самим.

Що потрібно робити, це:

  • додати, які пакунки слід використовувати, якщо ви використовуєте будь - який (використовуючи library())
  • якщо ви відкриваєте з'єднання або створюєте файли, додайте код, щоб закрити їх або видалити файли (використовуючи unlink())
  • якщо ви змінюєте параметри, переконайтеся, що код містить заяву, щоб повернути їх до вихідних. (напр op <- par(mfrow=c(1,2)) ...some code... par(op) )
  • Тест запустіть ваш код у новому, порожньому сеансі R, щоб переконатися, що код виконується. Люди повинні мати змогу просто скопіювати свої дані та код у консоль та отримати точно так само, як у вас.

Надайте додаткову інформацію

У більшості випадків достатньо лише R-версії та операційної системи. Коли виникають конфлікти з пакетами, дають вихід з sessionInfo() може дійсно допомогти. Якщо говорити про зв'язки з іншими програмами (будь то через ODBC чи щось інше), то також слід вказати номери версій для них, а також, якщо можливо, необхідну інформацію про налаштування.

Якщо ви використовуєте R в R Studio використовуючи rstudioapi::versionInfo() може бути корисним повідомити про вашу версію RStudio.

Якщо у вас виникла проблема з певним пакунком, ви можете надати версію пакета, видавши вихід packageVersion("name of the package").


1454



Як ти використовуєш dput якщо кадр даних дуже великий, а проблема спричинена серединою інформаційного фрейму? Чи є спосіб використання dput відтворити середній розділ даних, скажімо, рядки від 60 до 70? - BgnR
@BgnR Ви можете витягти частину кадру даних за допомогою індексів, наприклад: tmp <- mydf[50:70,] слідує dput(mydf). Якщо кадр даних дійсно великий, спробуйте виділити проблему і просто надішліть кілька рядків, які спричиняють проблему. - Joris Meys
@ JorisMeys: чи є спосіб сказати head або dput для обмеження даних рекурсивним рівнем N? Я намагаюся представити відтворений приклад, а моїми даними є список кадрів даних. Так, dput(head(myDataObj)) здається недостатньо, оскільки він генерує вихідний файл розміром 14 МБ. - Aleksandr Blekh
@JorisMeys: Просто FYI - опублікував питання в коментарях вище, як окремий питання: stackoverflow.com/questions/25127026/.... - Aleksandr Blekh
@Konrad Найкраще, що ви можете зробити, це посилання на файл і мінімальна команда для читання в цьому файлі. Це буде менше клопоту, ніж копіювання на виході dput () :) - Joris Meys


(Ось моя порада від Як написати відтворюваний приклад . Я намагався зробити це коротким, але солодким)

Як написати відтворюваний приклад.

Ви, швидше за все, отримаєте добру допомогу з вашою проблемою R, якщо ви надаєте відтворений приклад. Відтворений приклад дозволяє когось іншого відтворити вашу проблему, просто копіюючи та вставлення R-коду.

Є чотири речі, які потрібно включити, щоб ваш приклад можна було відтворити: необхідні пакунки, дані, код та опис вашого середовища R.

  • Пакетиповинен бути завантажений у верхній частині скрипту, так що це легко перегляньте, які приклади потрібні.

  • Найпростіший спосіб включити дані в електронному або переповненому стані питання полягає в тому, щоб використовувати dput() генерувати код R, щоб відтворити його. Наприклад, відтворити mtcars набір даних в R, Я виконаю наступні кроки:

    1. Біжи dput(mtcars) в Р.
    2. Скопіюйте вивід
    3. У моєму відтворюваному скрипті введіть mtcars <- потім вставте
  • Витратьте трохи часу на те, щоб ваше код це легко для інших читати:

    • переконайтеся, що ви використовували пробіли, а ваші імена змінної є лаконічними, але інформативний

    • використовуйте коментарі, щоб вказати, де ваша проблема лежить

    • Зробіть все можливе, щоб видалити все, що не пов'язане з проблемою.
      Чим коротший ваш код, тим простіше це зрозуміти.

  • Включити вихід з sessionInfo() в коментарях у вашому коді. Це узагальнює вашу Р. навколишнє середовище і дозволяє легко перевірити, чи використовуєте ви застарілу пакет

Ви можете перевірити, чи дійсно ви зробили відтворений приклад, запустивши свіжу сесію R та вставивши свій сценарій.

Перш ніж помістити весь ваш код в електронний лист, подумайте про його введення Гіст гітхуб . Це дасть вашому кодексу гарний підсвічування синтаксису, і вам не доведеться турбуватися про все, що потрапляє в систему електронної пошти.


514



reprex в tidyverse це хороший пакет для створення мінімального, відтворюваного прикладу: github.com/tidyverse/reprex - mt1022
Чому хтось поставить код у електронному листі? - Gilgamesh
Я регулярно отримую електронні листи з кодом у них. Я навіть отримую електронні листи з доданими документами з текстом, які містять код. Іноді я навіть отримую електронні листи з доданими текстовими документами, які містять екранні коди. - hadley


Особисто я віддаю перевагу "одному" вкладишу. Щось подібне:

my.df <- data.frame(col1 = sample(c(1,2), 10, replace = TRUE),
        col2 = as.factor(sample(10)), col3 = letters[1:10],
        col4 = sample(c(TRUE, FALSE), 10, replace = TRUE))
my.list <- list(list1 = my.df, list2 = my.df[3], list3 = letters)

Структура даних повинна імітувати ідею проблеми письменника, а не точної дослівної структури. Я дійсно ціную це, коли змінні не перезаписують мої власні змінні або боже, забороняють, функції (наприклад df)

Крім того, можна було б скоротити кілька кутів і вказати на наявний набір даних, що на кшталт:

library(vegan)
data(varespec)
ord <- metaMDS(varespec)

Не забудьте згадати будь-які спеціальні пакети, які ви можете використовувати.

Якщо ви намагаєтесь продемонструвати щось на більших об'єктах, ви можете спробувати

my.df2 <- data.frame(a = sample(10e6), b = sample(letters, 10e6, replace = TRUE))

Якщо ви працюєте з просторовими даними через raster пакет, ви можете генерувати деякі випадкові дані. Багато прикладів можна знайти у віньєтці пакунків, але ось невеликий тигр.

library(raster)
r1 <- r2 <- r3 <- raster(nrow=10, ncol=10)
values(r1) <- runif(ncell(r1))
values(r2) <- runif(ncell(r2))
values(r3) <- runif(ncell(r3))
s <- stack(r1, r2, r3)

Якщо вам потрібен якийсь просторовий об'єкт, який реалізується в sp, ви можете отримати деякі набори даних за допомогою зовнішніх файлів (наприклад, shapefile ESRI) у "просторових" пакетах (див. Просторовий вигляд у представленнях завдань).

library(rgdal)
ogrDrivers()
dsn <- system.file("vectors", package = "rgdal")[1]
ogrListLayers(dsn)
ogrInfo(dsn=dsn, layer="cities")
cities <- readOGR(dsn=dsn, layer="cities")

258



ІМХО, при використанні sample або runif це розумно set.seed. Принаймні, це пропозиція, яку я отримав під час створення прикладів ретрансляції зразків чи генерації випадкових чисел. - Konrad
@Konrad я згоден, але це може залежати. Якщо ви просто намагаєтеся створити кілька чисел, насіння може бути не потрібним, але якщо ви намагаєтесь зрозуміти щось конкретне, де потрібні фіксовані номери, насіння буде обов'язковим. - Roman Luštrik


Натхненний цим самим постом, тепер я використовую зручну функцію
reproduce(<mydata>) коли мені потрібно опублікувати в StackOverflow.


ШВИДКІ ІНСТРУКЦІЇ

Якщо myData це ім'я вашого об'єкта для відтворення, запустіть наступне в R:

install.packages("devtools")
library(devtools)
source_url("https://raw.github.com/rsaporta/pubR/gitbranch/reproduce.R")

reproduce(myData)

Подробиці:

Ця функція є інтелектуальною обгорткою до dput і робить наступне:

  • автоматично знімає великий набір даних (залежно від розміру та класу. Можна налаштувати розмір вибірки)
  • створює a dput вихід
  • дозволяє вказати котрий колонки для експорту
  • додає до фронту objName <- ... так що його можна легко скопіювати + вставити, але ...
  • Якщо ви працюєте на Mac, вивід автоматично скопіюється в буфер обміну, так що ви можете просто запустити його, а потім вставити на своє запитання.

Джерело доступне тут:


Приклад:

# sample data
DF <- data.frame(id=rep(LETTERS, each=4)[1:100], replicate(100, sample(1001, 100)), Class=sample(c("Yes", "No"), 100, TRUE))

DF становить близько 100 x 102. Я хочу зібрати 10 рядків і кілька конкретних стовпців

reproduce(DF, cols=c("id", "X1", "X73", "Class"))  # I could also specify the column number. 

Дає наступний висновок:

This is what the sample looks like: 

    id  X1 X73 Class
1    A 266 960   Yes
2    A 373 315    No            Notice the selection split 
3    A 573 208    No           (which can be turned off)
4    A 907 850   Yes
5    B 202  46   Yes         
6    B 895 969   Yes   <~~~ 70 % of selection is from the top rows
7    B 940 928    No
98   Y 371 171   Yes          
99   Y 733 364   Yes   <~~~ 30 % of selection is from the bottom rows.  
100  Y 546 641    No        


    ==X==============================================================X==
         Copy+Paste this part. (If on a Mac, it is already copied!)
    ==X==============================================================X==

 DF <- structure(list(id = structure(c(1L, 1L, 1L, 1L, 2L, 2L, 2L, 25L, 25L, 25L), .Label = c("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y"), class = "factor"), X1 = c(266L, 373L, 573L, 907L, 202L, 895L, 940L, 371L, 733L, 546L), X73 = c(960L, 315L, 208L, 850L, 46L, 969L, 928L, 171L, 364L, 641L), Class = structure(c(2L, 1L, 1L, 2L, 2L, 2L, 1L, 2L, 2L, 1L), .Label = c("No", "Yes"), class = "factor")), .Names = c("id", "X1", "X73", "Class"), class = "data.frame", row.names = c(1L, 2L, 3L, 4L, 5L, 6L, 7L, 98L, 99L, 100L)) 

    ==X==============================================================X==

Зверніть увагу також на те, що всі виходи знаходяться в хорошому одиничному, довгій рядку, а не в високому абзаці з нарізаних рядків. Це полегшує читання на запитаннях SO питання, а також легше копіювати + вставляти.


Оновлення жовтня 2013 року:

Тепер ви можете вказати, скільки рядків текстового виводу займе (тобто те, що ви будете вставляти в StackOverflow). Використовувати lines.out=n Аргумент для цього. Приклад:

reproduce(DF, cols=c(1:3, 17, 23), lines.out=7) дає:

    ==X==============================================================X==
         Copy+Paste this part. (If on a Mac, it is already copied!)
    ==X==============================================================X==

 DF <- structure(list(id = structure(c(1L, 1L, 1L, 1L, 2L, 2L, 2L, 25L,25L, 25L), .Label
      = c("A", "B", "C", "D", "E", "F", "G", "H","I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U","V", "W", "X", "Y"), class = "factor"),
      X1 = c(809L, 81L, 862L,747L, 224L, 721L, 310L, 53L, 853L, 642L),
      X2 = c(926L, 409L,825L, 702L, 803L, 63L, 319L, 941L, 598L, 830L),
      X16 = c(447L,164L, 8L, 775L, 471L, 196L, 30L, 420L, 47L, 327L),
      X22 = c(335L,164L, 503L, 407L, 662L, 139L, 111L, 721L, 340L, 178L)), .Names = c("id","X1",
      "X2", "X16", "X22"), class = "data.frame", row.names = c(1L,2L, 3L, 4L, 5L, 6L, 7L, 98L, 99L, 100L))

    ==X==============================================================X==

241





Ось хороший гід:

http://www.r-bloggers.com/three-tips-for-posting-good-questions-to-r-help-and-stack-overflow/

Але найголовніше: просто переконайтеся, що ви зробите невеликий фрагмент коду, який ми можемо запустити, щоб побачити, що таке проблема. Для цього корисна функція dput(), але якщо у вас дуже великі дані, можливо, вам знадобиться зробити малий набір зразків або використовувати лише перші 10 рядків або близько того.

EDIT:

Також переконайтеся, що ви визначили, де саме це проблема. Приклад не повинен являти собою весь скрипт R з "On line 200 there is an error". Якщо ви використовуєте інструменти налагодження в R (я люблю browser()) і Google, ви повинні мати можливість дійсно визначити, де ця проблема, і відтворити тривіальний приклад, в якому те ж саме йде не так.


168





У списку розсилки R-help є Поштовий гід який охоплює як запитання, так і відповіді на питання, включаючи приклад створення даних:

Приклади: іноді це допомагає   навести невеликий приклад, що хтось   може реально бігти. Наприклад:

Якщо матриця x має такий вигляд:

  > x <- matrix(1:8, nrow=4, ncol=2,
                dimnames=list(c("A","B","C","D"), c("x","y"))
  > x
    x y
  A 1 5
  B 2 6
  C 3 7
  D 4 8
  >

як я можу перетворити його на кадр даних   з 8 рядами і трьома колонками з назвою   'рядок', 'col' і 'value', які мають   назви розмірів як значення "рядка" та "col", наприклад:

  > x.df
     row col value
  1    A   x      1

...
  (До якого може бути відповідь:

  > x.df <- reshape(data.frame(row=rownames(x), x), direction="long",
                    varying=list(colnames(x)), times=colnames(x),
                    v.names="value", timevar="col", idvar="row")

)

Слово маленький особливо важливо. Ви повинні бути спрямовані на a мінімальний відтворюваний приклад, що означає, що дані та код повинні бути максимально простими, щоб пояснити проблему.

EDIT: Досить кодексу легше читати, ніж потворний код. Використовуйте a путівник по стилю.


142





Оскільки R.2.14 (я думаю), ви можете подати своє представлення текстових даних безпосередньо на read.table:

df <- read.table(header=T, text="Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa
4          4.6         3.1          1.5         0.2  setosa
5          5.0         3.6          1.4         0.2  setosa
6          5.4         3.9          1.7         0.4  setosa
") 

136



Ви також можете використовувати read.table("clipboard", header=TRUE). - sebastian-c
@ sebastian-c, як це добре для того, щоб зробити відтворений приклад ?? :) - TMS
@ TMS. Надаючи це серйозну думку, якщо запитувач надавав дані, і проблема мала (але може бути кілька рішень), вона може бути швидшою, і ви все ще можете виконати всі кроки. - sebastian-c


Іноді проблема дійсно не відтворюється з меншим об'ємом даних незалежно від того, наскільки важко ви спробуєте, і це не відбувається з синтетичними даними (хоча корисно показати, як ви виготовили синтетичні набори даних, які зробили ні відтворити проблему, оскільки вона виключає деякі гіпотези).

  • Публікація даних в Інтернеті де-небудь та надання URL-адреси можуть бути необхідними.
  • Якщо дані не можуть бути випущені широкій публіці, але вони можуть бути доступними взагалі, то ви можете запропонувати надіслати його зацікавленим особам (хоча це зменшить кількість людей, які змушені працювати на ньому).
  • Я насправді не бачив це зробити, оскільки люди, які не можуть випустити свої дані, чутливі щодо того, як звільнити їх у будь-якій формі, але, здавалося б, правдоподібно, що в деяких випадках все-таки можна було публікувати дані, якщо вони були достатньо анонізовані / трохи розбиті / пошкоджені якимось чином.

Якщо ви не можете робити жодного з цих, то вам, напевно, потрібно найняти консультанта для вирішення вашої проблеми ...

редагувати: Два корисних запитання SO для анонімності / скремблювання:


126



Для створення синтетичних наборів даних відповіді на це питання дайте корисні приклади, включаючи програми fitdistr і fitdistrplus. - Iterator