Așa cum am discutat deja în această vinie a pachetului meu {rix}, este foarte ușor să rulați {targets}
conductă în interiorul unui mediu NIX pentru o reproducere crescută. Principalul dezavantaj al {targets}
Cu toate acestea, este că nu este posibil să se calculeze un anumit obiect într -un anumit mediu și un alt obiect într -un alt mediu. De asemenea, nu este posibil să calculați o țintă folosind Python, de exemplu, cu excepția cazului în care utilizați {reticulate}
.
Dar putem face un pas mai departe: vedeți, NIX este un instrument foarte versatil, iar limbajul de programare NIX este un limbaj specific domeniului realizat pentru software-ul de pachete. Dacă presupuneți că, să spunem, un model statistic sau de învățare automată este doar un software, atunci de ce să nu folosiți NIX pentru a -l construi? Acest gând este ceea ce m -a făcut să vreau să scriu {rixpress}
.
rixpress, un pachet pentru a defini conductele analitice reproductibile
Limbajul de programare NIX este un limbaj specific domeniului utilizat pentru a ambala și construi software, iar „software” poate avea o definiție foarte largă. După cum am explorat în această postare pe blog, NIX (limbajul de programare) poate fi utilizat pentru a defini o conductă de poliglot pentru a construi, de exemplu, un raport quarto folosind R și Python. Acum am construit un pachet numit {rixpress}
care este puternic inspirat de {targets}
(Dacă nu sunteți familiarizat cu {targets}
Îl introduc la sfârșitul acestei postări pe blog) pentru a genera astfel de conducte și a le construi folosind NIX. Mai jos este un exemplu complet care începe prin utilizarea Python și Biblioteca Polars pentru a încărca un set de date, apoi îl transformă un pic și transformă datele într -un Pandas DataFrame, apoi îl trece la R (conversia se face prin intermediul reticulate::py_load_object()
Sub capotă, de asemenea, de ce a trebuit să convertesc DataFrame Polars într -un Pandas DataFrame) și, în sfârșit, să compilez un document quarto (puteți găsi codul aici):
library(rixpress) d0 <- rxp_py_file( name = mtcars_pl, path="data/mtcars.csv", read_function = "lambda x: polars.read_csv(x, separator="|")", nix_env = "py-env.nix" ) d1 <- rxp_py( # reticulate doesn't support polars DFs yet, so need to convert # first to pandas DF name = mtcars_pl_am, py_expr = "mtcars_pl.filter(polars.col('am') == 1).to_pandas()", nix_env = "py-env.nix" ) d2 <- rxp_py2r( name = mtcars_am, expr = mtcars_pl_am ) d3 <- rxp_r( name = mtcars_head, expr = my_head(mtcars_am), additional_files = "functions.R" ) d4 <- rxp_r( name = mtcars_tail, expr = tail(mtcars_head) ) d5 <- rxp_r( name = mtcars_mpg, expr = dplyr::select(mtcars_tail, mpg) ) doc <- rxp_quarto( name = page, qmd_file = "page.qmd", additional_files = c("content.qmd", "images"), nix_env = "quarto-env.nix" ) rxp_list <- list(d0, d1, d2, d3, d4, d5, doc) rixpress(rxp_list, project_path = ".") plot_dag()
Să parcurgem acest cod:
d0 <- rxp_py_file( name = mtcars_pl, path="data/mtcars.csv", read_function = "lambda x: polars.read_csv(x, separator="|")", nix_env = "py-env.nix" )
rxp_py_file()
Utilizează Python pentru a încărca un fișier local. În acest caz, este mtcars.csv
set de date sub data/
pliant. Funcția de citire trebuie să fie o funcție a unui singur parametru, calea către date, așa că folosesc o ambalare a funcțiilor anonime polars.read_csv
ceea ce îmi permite să setez separatorul pe țeava Unix |
. De asemenea, acest cod este executat în mediul înconjurător definit de py-env.nix
fişier. Acest fișier poate fi generat de celălalt pachet, {rix}
și listează pachetele Python necesare (îl veți găsi în repo).
Apoi:
d1 <- rxp_py( # reticulate doesn't support polars DFs yet, so need to convert # first to pandas DF name = mtcars_pl_am, py_expr = "mtcars_pl.filter(polars.col('am') == 1).to_pandas()", nix_env = "py-env.nix" )
rxp_py()
execută codul Python și salvează ieșirea în name
argument. În acest caz, filtrez DataFrame Polars și îl convertesc într -un DataFrame Pandas. Acest lucru se întâmplă din nou în interiorul mediului definit de py-env.nix
este un python pur env, nu {reticulate}
necesar în această etapă.
Apoi:
d2 <- rxp_py2r( name = mtcars_am, expr = mtcars_pl_am )
rxp_py2r()
apeluri reticulate::py_load_object()
Pentru a converti DataFrame Pandas într -un R DataFrame. Acum putem continua să -l utilizăm folosind R! Veți observa că nu nix_env
argumentul este transmis acestei funcții. Când nu este furnizat niciun argument nix_env
mediul implicit, default.nix
se obișnuiește. Acesta trebuie să fie întotdeauna prezent și, în acest caz, conține pachetele R necesare pentru conductă.
Apoi:
d3 <- rxp_r( name = mtcars_head, expr = my_head(mtcars_am), additional_files = "functions.R" )
Acesta folosește un argument pe care nu îl știm încă, additional_files
. Vă permite să treceți scripturi R care definesc funcțiile. În acest caz, functions.R
conține definiția my_head()
care este utilizat pe mtcars_am
.
d4
şi d5
sunt auto-explicative, așa că acum să aruncăm o privire rxp_quarto()
:
doc <- rxp_quarto( name = page, qmd_file = "page.qmd", additional_files = c("content.qmd", "images"), nix_env = "quarto-env.nix" )
Aceasta compilează page.qmd
document, care necesită fișiere suplimentare: content.qmd
care este inclus în page.qmd
și images/
folder, care conține imagini necesare pentru compilarea documentului. Acest fișier este compilat folosind quarto-env.nix
mediu.
Punerea tuturor acestor derivate într -o listă și trecerea acesteia la rixpress()
încă nu construiește conducta, dar generează un pipeline.nix
Fișier care este expresia NIX care va construi ieșirea, în acest caz, documentul nostru Quarto. De asemenea, puteți arunca o privire la DAG folosind plot_dag()
:
Și este, de asemenea, posibil să preiați obiecte într -o sesiuni interactive folosind rxp_read()
(pentru a le citi) sau rxp_load()
(Pentru a le încărca în mediul global). Când citiți sau încărcați obiecte Python, acest lucru va fi convertit folosind {reticulate}
pe zbor.
Pentru a construi conducta, rulați rxp_make()
. Rulările ulterioare nu construiesc totul, deoarece ieșirile intermediare sunt memorate în cache în Magazin Nix. Așadar, dacă schimbați doar documentul Quarto, doar această derivare este construită din nou. De asemenea, este posibil să exportați și să importați rezultatele folosind export_nix_archive()
şi import_nix_archive()
destul de util pentru CI!
Avertismente
Acest pachet este încă în etapa prototipului, așa că nu -l folosiți pentru nimic serios. Există încă unele lucruri la care trebuie să lucrez, pentru că deocamdată depanarea unei conducte defectuoase este într -adevăr grea, deoarece producțiile intermediare sunt dificil de găsit dacă conducta nu a fost construită complet.
De asemenea, datorită modului în care funcționează NIX, fiecare calcul se întâmplă într -o cutie de nisip complet izolată. Acesta este motivul pentru care rxp_*()
Funcțiile au asta additional_files
Argument, deoarece în cazul în care este necesar ceva extern, NIX trebuie să -l copieze în cutia de nisip. Aceasta înseamnă, de asemenea, că funcțiile care necesită acces la internet la muncă vor eșua. Dar am putut să lucrez în jurul asta pentru rxp_file()
: Deci, dacă o resursă este online, funcția care o citește ar trebui să poată ajunge la ea.
Acum, permiteți -mi să vă prezint {targets}
principala mea sursă de inspirație pentru acest pachet
Pachetul de ținte, sursa mea de inspirație pentru rixpress
Sunt un fan imens al {targets}
Pachet și credeți că este cu adevărat unul dintre cele mai bune pachete realizate vreodată. În opinia mea, niciun alt instrument de automatizare a construirii/conductelor nu se apropie. Majoritatea acestor instrumente necesită să vă definiți conducta într -o altă limbă (cum ar fi YAML) sau să vă obligați să utilizați o sintaxă foarte specifică în care trebuie să definiți obiectele pentru a calcula, intrările și ieșirile lor. Dar {targets}
Vă permite să definiți conducta dvs. ca o serie de apeluri R:
# _targets.R file library(targets) library(tarchetypes) tar_source() tar_option_set(packages = c("readr", "dplyr", "ggplot2")) list( tar_target(file, "data.csv", format = "file"), tar_target(data, get_data(file)), tar_target(model, fit_model(data)), tar_target(plot, plot_model(model, data)) )
Acest lucru poate părea străin pentru mulți utilizatori R, dar dacă te uiți cu atenție, îți vei da seama că cea mai mare parte a acestui cod este Boilerplate:
# _targets.R file library(targets) library(tarchetypes) tar_source() tar_option_set(packages = c("readr", "dplyr", "ggplot2")) list( tar_target(....), tar_target(....), tar_target(....), tar_target(....) )
și ceea ce contează este definit în interiorul tar_target()
Funcții. Scoateți placa de cazan și ajungeți cu codul R esențial corect, după câteva ajustări:
file <- "data.csv" data <- get_data(file) model <- fit_model(data) plot <- plot_model(model, data)
Dar de ce să treci prin probleme de utilizare {targets}
? Ei bine, cel mai mare motiv este că {targets}
își dă seama de dependențele dintre obiectele pe care doriți să le calculați și le memorați în cache. Deci, în exemplul de mai sus, dacă schimbați codul doar fit_model()
Funcție, numai model
şi plot
sunt re-calculate. Dar dacă schimbați file
și îndreptați calea către o actualizare data.csv
Fișier, apoi totul este calculat din nou. Urmăriți videoclipul introdus din pasul oficial pentru o explicație vizuală: dar aveți încredere în mine, {targets}
se află în această clasă de instrumente care te fac să te întrebi cum ai fi putut face ceva înainte de a -l folosi.
Concluzie
Cred că asta {rixpress}
Poate deveni un pachet destul de util, așa că probabil îl voi trimite pentru Revizuirea de la Ropensci Peer la timp.
Și mulțumită Grant McDermott pentru că a sugerat numele „Rixpress”!