Egy kolléga átadott egy statisztikai elemzést R-ben: logisztikus
regressziót futtatott a Northstar kezdőlabor orders.csv
adatán, és azt állítja, hogy az eredmény bárhol reprodukálható. A te
feladatod ellenőrizni, hogy a környezetleírás valóban elég-e egy másik
gépen történő újrafuttatáshoz.
<repo>/lab/orders.csv tiszta, 2400 rendelést
tartalmazó fájl; egy R projektmappa, amelyben
northstar-r.Rproj, néhány R script és egy
renv.lock található.sessionInfo() kimenetet; ellenőrizd,
hogy a Python-megfelelőkhöz hasonlóan melyik réteg mit rögzít.renv.lock
legalább az R-verziót és a readr csomagot a függőségeivel
együtt tartalmazza; a sessionInfo() a readr
verzióját és az R-verziószámot írja ki. A kontrollfuttatás során a
CSV-ből 2400 sort és 15 oszlopot várok.Az R-környezeteknél gyakori félreértés, hogy az emberek egyetlen eszközként beszélnek „az R-ről”. Valójában legalább négy réteget érdemes különválasztani; ez a gondolatmenet pontosan megfelel a Pythonnál már megismert felosztásnak (Kernelregisztráció és tiszta újrafuttatás).
Az R (szűk értelemben R interpreter,
magyarul R-értelmező) az a program, amely a .R
kiterjesztésű kódot végrehajtja. Ugyanolyan szerepe van, mint a Python
interpreternek. A verzióját az
R.version.stringkifejezés adja meg, például
"R version 4.4.1 (2024-06-14)". Az interpreter saját
telepítési gyökérrel rendelkezik, amelyet az R.home() hívás
mutat meg. Ez a könyvtár tartalmazza a base csomagokat és a futtató
binárist. A verziószám első két tagja, például 4.4,
gyakorlati szempontból fontos: az ilyen szintű eltérés esetén egyes
bináris csomagok nem hordozhatók át automatikusan.
A Python-megfelelő: sys.version és
sys.executable. A Kernelregisztráció és
tiszta újrafuttatás alfejezetben tanult kernelspec éppen azt a hibát
előzi meg, amikor egy notebook más Python-interpretert használ, mint a
CLI. R-ben ugyanez a probléma a Posit IDE és a terminál között léphet
fel.
A Posit IDE (korábbi nevén RStudio, angolul
integrated development environment) szerkesztőt,
projektnézetet, konzolt és debuggert kínál. Fontos: a Posit IDE nem maga
az R, hanem egy felület, amely egy kiválasztott R-interpreterhez
csatlakozik. Ezért fordulhat elő, hogy az IDE-ben R.home()
más értéket ad, mint a terminálban kiadott R parancs, ha a
beállításokban eltérő verziót választottunk.
A Python világában a VS Code vagy a JupyterLab is hasonló szerepet tölt be: a megnyitott projekt mappája és a kiválasztott kernel/interpreter két külön dolog. A Jupyter kernelspec regisztrálásakor (Kernelregisztráció és tiszta újrafuttatás) is láthattuk, hogy a felületi név nem azonos a tényleges interpreter-útvonallal.
Az R csomagok a library path (könyvtárútvonal) nevű mappaegyüttesből töltődnek be. Ezt az R-munkamenet az
.libPaths()vektorral listázza. A vektor elemei teljes elérési utak; az első elem a legmagasabb prioritású könyvtár. Egy projektbe zárt rendszerben az első elem például:
# [1] "/home/user/northstar-r/renv/library/R-4.4/x86_64-pc-linux-gnu"
# [2] "/usr/local/lib/R/site-library"
# [3] "/usr/lib/R/library"Ez a példa háromelemű vektor, szemléltető értékű; az első elem a
projekt könyvtára, a második a gépszintű telepítés, a harmadik pedig a
base csomagokat tartalmazó rendszerkönyvtár. A fontos ellenőrzési pont,
hogy egy betöltött csomag valójában honnan érkezik:
find.package("readr") megadja a points elérési utat. Ha az
út nem a projekt renv/library/... elejére mutat, akkor a
projekt nem izolált.
Az renv (R environment) a csomagok verzióit és a
függőségi gráfot rögzíti, nem pedig egy hordozható
interpreter-környezetet készít. A renv.lock egy JSON-fájl,
amelyben például a következő blokk látható:
{
"R": {
"Version": "4.4.1",
"Repositories": [
{
"Name": "CRAN",
"URL": "https://cloud.r-project.org"
}
]
},
"Packages": {
"readr": {
"Package": "readr",
"Version": "2.1.5",
"Source": "Repository",
"Repository": "CRAN",
"Hash": "9c7f6c7e8b2c3a0e4f5d6e7a8b9c0d1e"
}
}
}A fenti Hash érték illusztráció; valós lockfájlban a
renv saját ellenőrző lenyomata szerepel. A lockfile tehát azt mondja
meg, hogy egy másik gépen melyik verziókat kell visszaállítani;
magát az R interpreters telepítőt nem tartalmazza.
A Python-megfelelők kétfelé válnak. A venv inkább a
renv/library mappa analógiája: izolált csomagtér egy adott
gépen, amelyet nem hordozunk gépek között. Az uv.lock a
renv.lock analógiája: verziókat rögzít, de nem tartalmazza
sem a Python-buildet, sem a forrásfájlokat.
Az R project gyakorlatilag egy mappa, amelyet az
.Rproj kiterjesztésű fájl fémjelez. A projekt-munkafolyamat
előnye, hogy a relatív elérési utak a projektgyökérből indulnak, a
scriptek és a data/, artifacts/ mappák helye
egyértelmű.
A sessionInfo() az aktuális R-munkamenet auditja. Tartalmazza:
readr-t, ha library(readr) után hívtuk),Fontos különbség: a sessionInfo() csak az aktuálisan
betöltött csomagokról szól, míg a renv.lock a projekt
teljes függőségi gráfját rögzítheti. Ezért a reprodukálhatósági
auditban mindkettőre szükség van: a lock a célt adja meg, a
sessionInfo() pedig az aktuális munkamenet tényleges
állapotát igazolja.
A következő példa egy Linux/macOS gépen, a könyv repo gyökeréből
indítható. Ha a tanuló Windows PowerShellt használ, a cp és
mkdir parancsokat a Copy-Item és
New-Item parancsok helyettesítik; az R-parancsok
platformfüggetlenek.
Első lépésben hozd létre az R-projekt mappát, és másold be a tiszta
orders.csv fájlt:
# platform: Linux/macOS; munkakönyvtár: a repo gyökere
mkdir -p northstar-r/data northstar-r/artifacts
cp lab/orders.csv northstar-r/data/orders.csv
cd northstar-rEzután futtasd az alábbi R-scriptet. A kód maga futtatható; a konkrét kimenet a géptől és a letöltött csomagverzióktól függ, ezért a kommenteket szemléltetőként jelölöm.
# setup_env.R
# Platform: Linux/macOS; futtatás a projektgyökérből:
# Rscript setup_env.R
if (!requireNamespace("renv", quietly = TRUE)) {
install.packages("renv")
}
# Projektizoláció létrehozása: renv.lock és renv/ mappa
renv::init()
# Csomag telepítése a projektkönyvtárba, majd lock-frissítés
renv::install("readr")
renv::snapshot()
# Adatbetöltés és kézi kontroll
df <- readr::read_csv("data/orders.csv", show_col_types = FALSE)
stopifnot(nrow(df) == 2400L)
# Munkamenet-audit mentése
sink("artifacts/sessioninfo_r.txt")
sessionInfo()
sink()
# Ellenőrző kinyerés
cat("R verzió:", R.version.string, "\n")
cat("readr verzió:", as.character(packageVersion("readr")), "\n")
cat("readr elérési út:", find.package("readr"), "\n")Várható, szemléltető kimenet a script logjából:
* Initializing project ...
* Installing readr ...
* Lockfile written to 'renv.lock'
R verzió: R version 4.4.1 (2024-06-14)
readr verzió: 2.1.5
readr elérési út: /home/user/northstar-r/renv/library/R-4.4/x86_64-pc-linux-gnu/readr
Az utolsó sor kulcsfontosságú: az elérési út arra a könyvtárra mutat,
amelyet a renv::init() hozott létre, nem pedig a gépszintű
site-library-ra. Ha ez a sor máshová mutat, a
projektizoláció nem működik.
A Python-megfelelőket összefoglaló környezettérkép:
| Szerep | R eszköz | Python-megfelelő a lab gyakorlatában |
|---|---|---|
| Nyelvi interpreter | R, R.home() |
python, sys.executable |
| Projektizoláció | renv + .Rproj |
venv + pyproject.toml |
| Verziórögzítés | renv.lock |
uv.lock |
| Kernelregisztráció | IRkernel |
ipykernel kernelspec (Kernelregisztráció és tiszta újrafuttatás) |
A minimális példa után két auditfájlunk van: a renv.lock
és az artifacts/sessioninfo_r.txt. Az értelmezés során a
következő invariantokat ellenőrizzük:
df shape-je
c(2400, 15). A 2400 sor a lab generálási szerződéséből
következik (II. ellenőrző pont: adatból ellenőrzött
feature-tábla); a 15 oszlop a rendeléskori 11 ismert mezőből és a 4
utólagos célmezőből adódik. Ez a kézi ellenőrzés azt igazolja, hogy a
betöltő kód a tiszta fájlt olvassa, nem az
orders_raw.csv-t.renv::status() üres
kimenetet ad, ha a projekt mappában lévő csomagok megegyeznek a lockkal.
Ha eltérést jelez, a renv::snapshot() hiányzott vagy a
könyvtárat kézzel módosították.sessionInfo()$otherPkgs$readr$Version értéke megegyezik a
packageVersion("readr") értékével, és a
renv.lock Packages$readr$Version mezőjével. A
verziók háromoldalú egyezése a reprodukálhatóság minimális
bizonyítéka.find.package("readr")
a projekt renv/library alá mutat. Ez ugyanaz a gondolat,
mint az II. ellenőrző pont: adatból ellenőrzött
feature-tábla alfejezetben a fájlhash-ellenőrzés: az állítás csak
akkor igaz, ha a tényleges beolvasott erőforrás is az, amit
állítunk.Egy sessionInfo() kivonat így kezdődik:
R version 4.4.1 (2024-06-14)
Platform: x86_64-pc-linux-gnu
other attached packages:
readr 2.1.5
A renv.lock ugyanakkor dplyr 1.1.4-et is
tartalmaz, de a sessionInfo() nem említi a
dplyr-t. Miért lehetséges ez? Melyik fájl mit bizonyít?
Írj egy verify_r_env.R scriptet, amely a
projektgyökérből futtatható, és a következőket ellenőrzi:
renv.lock létezik;readr betölthető;orders.csv 2400 sort tartalmaz;readr útvonala a renv/library alá
mutat;0 kilépési kóddal tér vissza, ha minden
igaz.A kolléga a Posit IDE-ben R.version.string értékeként
4.4.1-et lát, de a terminálban az R parancs
4.2.1-et ír ki. Azt állítja, hogy „a rendszer R-je hibás”.
Mi lehet a valódi magyarázat? Milyen parancsokkal deríted fel, és miért
nem a rendszer R-je hibás?
A projektet egy másik, Linux helyett macOS-es gépen szeretné újra
futtatni a kolléga, ahol az R 4.3.3 van telepítve, de még nincs
renv csomag. Mi az a három minimális lépés, amellyel a
környezetet visszaállítja? Adj meg egy korlátot, amely miatt a lock
önmagában nem garantálja a bitazonos eredményt.
A sessionInfo() csak az aktuális munkamenetben
betöltött csomagokat mutatja az
other attached packages szakaszban. A
renv.lock ezzel szemben a projekt teljes függőségi
gráfját rögzítheti, beleértve azokat a csomagokat is, amelyeket jelenleg
nem töltöttünk be a library() hívással. Elképzelhető, hogy
a dplyr korábban telepítve lett a projektbe, de a jelenlegi
script csak readr-t használ. A két fájl tehát nem
ellentmondásos: a lock a célt, a sessionInfo() a munkamenet
tényleges állapotát rögzíti. A reprodukálhatósági auditban a lock az
elsődleges forrás; a sessionInfo() azt igazolja, hogy az
aktuális futtatás a lock szerinti verziókat használta.
# verify_r_env.R
# Platform: Linux/macOS; futtatás: Rscript verify_r_env.R
stopifnot(file.exists("renv.lock"))
stopifnot(requireNamespace("readr", quietly = TRUE))
df <- readr::read_csv("data/orders.csv", show_col_types = FALSE)
stopifnot(nrow(df) == 2400L)
readr_path <- find.package("readr")
stopifnot(grepl("renv/library", readr_path, fixed = TRUE))
cat("OK: lock, readr, rows, path\n")Az ellenőrző invariant a négy stopifnot: a lock
létezése, a csomag betölthetősége, a sorok száma, valamint a
projektizolált elérési út. A script hibás környezetben nullától eltérő
kilépési kóddal áll le; ez a Kódminőség és
együttműködő review alfejezetben bevezetett automatikus belépési
küszöb R-megfelelője.
A Posit IDE nem az R terminálparancsot indítja, hanem a
beállításaiban megadott interpretert. Ezért a két felület eltérő
R.home() értéket adhat. A diagnózis:
Sys.which("R") # a terminál R parancsa
R.home() # az aktuális munkamenet gyökere
.libPaths() # a csomagkeresési sorrend
Sys.getenv("RSTUDIO_WHICH_R") # ha a Posit IDE beállítottaA megoldás az IDE-ben a Global Options → General → R version
beállítás egységesítése, nem a rendszer „javítása”. A
renv::restore() a terminálból a 4.2.1 alatt figyelmeztetést
adhat, mert a lock R/Version mezője 4.4.1-et tartalmaz. Ez
a jelenség pontosan a Kernelregisztráció és tiszta
újrafuttatás alfejezetben látott kernelspec-eltérés analógja.
A másik gépen, a projektmappa másolása után:
# 1. A renv csomag telepítése a felhasználói könyvtárba, ha még nincs
install.packages("renv")
# 2. A lockból a projektkönyvtár visszaállítása
renv::restore()
# 3. Ellenőrzés, hogy a projektcsomagok a locknak megfelelnek
renv::status()A korlát: a renv.lock az R-verziót és a csomagverziókat
rögzíti, de magát az R 4.4.1-et nem tartalmazza; a macOS gépen 4.3.3
fut, ezért a renv::restore() figyelmeztethet. Ráadásul a
bináris csomagok hash-ei platformfüggők lehetnek, így a
renv::status() nem bitazonossági bizonyíték; az R- és
csomagverziók, valamint a deklarált projektállapot konzisztenciáját
ellenőrzi. Ugyanez a helyzet a Python venv esetében is: a
.venv nem hordozható, az uv.lock viszont
az.
Tévedés: „A Posit IDE maga az R, és a renv ugyanaz, mint a Python venv, csak R-ben.”
Ellenpélda: Egy elemző a Posit IDE konzoljában
renv::init()-et futtat, majd azt hiszi, hogy a projekt
immár hordozható, mert a renv/library mappát felmásolja egy
pendrive-ra. A másik gépen a projekt nem fut, mert az interpreter
hiányzik, és a bináris csomagok sem egyeznek a másik operációs
rendszeren. A venv is hasonló: egy .venv mappa
átmásolása másik gépre nem garantálja a működést; a hordozható réteg a
lockfile. Az IDE pedig csak megjeleníti az interpretert; a tényleges
futtatókörnyezet az R.home() által mutatott program. Ezért
mindig a lock + sessionInfo() + find.package()
hármast validáld, ne a felület állapotát.
Tedd fel magadnak a következő három kérdést:
sessionInfo() R-verziója megegyezik a
renv.lock R/Version mezőjével?find.package() által mutatott útvonal a projekt
renv/library alá esik, nem a gépi site-library
alá?renv::restore()
majd renv::status() üres státuszt ad-e?Ha bármelyik válasz nem, akkor a környezeti reprodukálhatóság állítása hamis volt, még akkor is, ha a Script a saját gépén lefutott.
Az R-környezet négy rétegből áll: az R-interpreter, a Posit IDE, a
library path által leírt csomagkönyvtárak és a
renv.lock által rögzített csomagverziók. A projekt
.Rproj fájlja a munkakönyvtárat, a
sessionInfo() az aktuális munkamenetet auditálja. A
reprodukálhatósághoz nem elég a script; a lock, a munkamenet-audit és a
csomag-elérési út ellenőrzése együttesen bizonyítja, hogy a megfelelő
környezet futott. A következő alkalmazásban Tábla és
formula R-ben az R táblázatos adatszerkezeteit és
formula-interfészét használjuk, hogy ugyanezen a projektben statisztikai
modellt illesszünk és értelmezzünk.