37.1 R-környezet és statisztikai munkafolyamat

37.1.1 Cél és konkrét helyzet

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.

37.1.2 Nyelv, IDE, csomagkönyvtár és lock: négy különböző réteg

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).

37.1.2.1 Az R mint nyelvi futtatókörnyezet

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.string

kifejezé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.

37.1.2.2 Posit IDE: szerkesztő, nem interpreter

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.

37.1.2.3 Library path: hol élnek a csomagok

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.

37.1.2.4 renv: projektlock, nem interpreter-másolat

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.

37.1.2.5 project és sessionInfo

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:

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.

37.1.3 Teljes minimális munkapélda: környezettérkép

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-r

Ezutá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)

37.1.4 Eredmény értelmezése

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:

  1. Sor- és dimenziókontroll: 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.
  2. Lock-invariant: a 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.
  3. Session-invariant: a 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.
  4. Út-invariant: a 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.

37.1.5 Feladatok

37.1.5.1 1. Értelmezés

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?

37.1.5.2 2. Implementálás

Írj egy verify_r_env.R scriptet, amely a projektgyökérből futtatható, és a következőket ellenőrzi:

37.1.5.3 3. Hibadiagnózis

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?

37.1.5.4 4. Megváltozott feltétel: másik gépre átadá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.

37.1.6 Megoldások és indoklás

37.1.6.1 1. Értelmezés

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.

37.1.6.2 2. Implementálás

# 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.

37.1.6.3 3. Hibadiagnózis

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ította

A 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.

37.1.6.4 4. Megváltozott feltétel

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.

37.1.7 Gyakori tévedés és ellenpélda

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.

37.1.8 Önellenőrzés

Tedd fel magadnak a következő három kérdést:

  1. A sessionInfo() R-verziója megegyezik a renv.lock R/Version mezőjével?
  2. A find.package() által mutatott útvonal a projekt renv/library alá esik, nem a gépi site-library alá?
  3. Egy frissen klónozott projektmappában a 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.

37.1.9 Rövid összefoglaló

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.