requireNamespace("survival", quietly = TRUE)
requireNamespace("gibt.es.nicht", quietly = TRUE)
"survival" %in% rownames(installed.packages())[1] TRUE
scikit-learn gegen sklearn.sys.path, und der Ordner des Skripts steht vorn. Eine eigene random.py verdeckt die Standardbibliothek. In R verdeckt das zuletzt geladene Paket gleichnamige Funktionen.requirements.txt, renv.lock) und anderswo wiederhergestellt. Versioniert wird die Liste, nicht der Ordner."2.10.0" kleiner als "2.9.0".Vorwissen: Funktionen und Module in Python, weil ein Paket eine geordnete Sammlung von Modulen ist und der Import dort erklärt wird. Wie man mit festgehaltener Umgebung ganze Auswertungen wiederholbar macht, steht unter Reproduzierbarkeit; der Schritt darüber hinaus sind Container, siehe Docker.
Zwei Projekte brauchen dieselbe Bibliothek in verschiedenen Versionen. Wird global installiert, gibt es nur einen Stand, und eines der beiden Projekte ist immer kaputt. Die zweite Hälfte des Problems zeigt sich später: Eine Auswertung, die vor einem Jahr lief, liefert heute andere Zahlen oder bricht ab, weil eine Bibliothek ihr Verhalten geändert hat. Die Sammlung hier hat das selbst erlebt: pingouin hat zwischen zwei Builds seine Spaltennamen geändert.
Eine virtuelle Umgebung löst beides: ein Verzeichnis mit eigenem Interpreter und eigenen Paketen, das zu genau einem Projekt gehört, plus eine Liste der Versionen, aus der es sich neu erzeugen lässt.
Diese Befehle laufen in der Shell, nicht im Interpreter:
python -m venv .venv # Umgebung im Projektordner anlegen
.venv\Scripts\activate # aktivieren unter Windows
source .venv/bin/activate # aktivieren unter Linux und macOS
pip install numpy pandas
pip freeze > requirements.txt # Stand festhalten
pip install -r requirements.txt # Stand anderswo herstellen
deactivate| Aufgabe | Python mit pip | Python mit conda | R mit renv |
|---|---|---|---|
| Umgebung anlegen | python -m venv .venv |
conda create -n projekt python=3.12 |
renv::init() |
| aktivieren | source .venv/bin/activate |
conda activate projekt |
beim Öffnen des Projekts automatisch |
| Stand festhalten | pip freeze > requirements.txt |
conda env export --from-history > environment.yml |
renv::snapshot() |
| wiederherstellen | pip install -r requirements.txt |
conda env create -f environment.yml |
renv::restore() |
| was zu versionieren ist | requirements.txt |
environment.yml |
renv.lock |
| was nicht | .venv/ |
der Umgebungsordner | renv/library/ |
Das Verzeichnis der Umgebung gehört in .gitignore. Es ist gross, plattformabhängig und lässt sich aus der Liste jederzeit neu erzeugen; die Liste ist klein, lesbar und die eigentliche Information.
pip installiert aus PyPI und kümmert sich um Python-Pakete. Für die allermeisten Fälle genügt es. conda verwaltet zusätzlich nicht-pythonische Bestandteile, etwa kompilierte numerische Bibliotheken. Seit PyPI für gängige Pakete fertige Binärdateien mitliefert, ist dieser Vorteil kleiner geworden. Beide in derselben Umgebung zu mischen ist eine bekannte Quelle kaputter Installationen, weil jedes Werkzeug nur seine eigene Buchführung kennt: pro Projekt eines wählen.
Zeile in requirements.txt |
Bedeutung | wann sinnvoll |
|---|---|---|
pandas |
irgendeine Version | fast nie |
pandas>=2.0 |
mindestens diese | Bibliotheken, die andere einbinden |
pandas~=2.2 |
mindestens 2.2, aber unter 3 | Fehlerbehebungen ja, grosse Sprünge nein |
pandas==2.2.1 |
genau diese | Auswertungen, die reproduzierbar bleiben sollen |
Eine Hauptversion (die erste Zahl) darf nach verbreiteter Konvention Bestehendes brechen. Deshalb ist <3 oder ~=2.2 für Bibliotheken ein vernünftiger Kompromiss, == für eine abgeschlossene Auswertung.
| Aufgabe | R | Python |
|---|---|---|
| ist ein Paket vorhanden? | requireNamespace("x", quietly = TRUE) |
importlib.util.find_spec("x") is not None |
| installierte Version | packageVersion("dplyr") |
importlib.metadata.version("pandas") |
| welcher Interpreter läuft | R.home(), R.version.string |
sys.executable, sys.version |
| wo wird gesucht | .libPaths() |
sys.path |
| woher kommt eine Funktion | find("filter") |
modul.__file__ |
| Funktion eines bestimmten Pakets | stats::filter() |
import modul und modul.funktion() |
| Stand für den Bericht | sessionInfo() |
pip freeze |
Drei Pakete der Build-Umgebung dieser Seite: scikit-learn, pandas und SciPy. Unter welchem Namen sind sie installiert, unter welchem werden sie importiert, und wie findet man den einen aus dem anderen?
for verteilung, modul in [("scikit-learn", "sklearn"), ("pandas", "pandas"),
("scipy", "scipy")]:
try:
_ = md.version(verteilung)
installiert = True
except md.PackageNotFoundError:
installiert = False
importierbar = importlib.util.find_spec(modul) is not None
print(f"{verteilung:13s} {modul:9s} {installiert} {importierbar}")scikit-learn sklearn True True
pandas pandas True True
scipy scipy True True
None
['scikit-learn']
| Zeile | Ausgabe | Erklärung |
|---|---|---|
| scikit-learn | installiert True, als sklearn importierbar True |
Installiert wird scikit-learn, importiert wird sklearn. |
| pandas, scipy | beides True |
Hier sind die Namen gleich, der Normalfall. |
find_spec("scikit_learn") |
None |
Unter dem Installationsnamen gibt es kein Modul. |
packages_distributions() |
['scikit-learn'] |
Der Weg zurück: Welche Distribution liefert das Modul sklearn? |
| R: vorhandenes Paket | TRUE |
|
| R: fehlendes Paket | FALSE |
requireNamespace prüft, ohne abzubrechen, anders als library(). |
| R: Liste der installierten | TRUE |
In R sind Installations- und Ladename immer gleich. |
Wer pip install sklearn tippt, bekommt heute eine Fehlermeldung, früher sogar ein leeres Platzhalterpaket. Wer nach dem Importnamen sucht, findet das Paket in pip list nicht. Die Unterscheidung klingt pedantisch und spart bei jeder gescheiterten Installation Zeit. In R stellt sich die Frage nicht.
scikit-learn ist unter diesem Namen installiert und wird als
sklearnimportiert; unter dem Installationsnamen findet Python kein Modul. In R sind Installations- und Ladename identisch, undrequireNamespaceprüft das Vorhandensein, ohne abzubrechen.
Im Ordner einer Auswertung liegt eine eigene Datei random.py. Das Skript auswertung.py im selben Ordner importiert random und prüft, ob es randint gibt. Beide Dateien werden in einem temporären Ordner angelegt, das Skript läuft als eigener Prozess. In R der verwandte Fall: dplyr und stats haben beide eine Funktion filter.
ordner = pathlib.Path(tempfile.mkdtemp())
_ = (ordner / "random.py").write_text(
"print('eigene random.py geladen')\n", encoding="utf-8")
_ = (ordner / "auswertung.py").write_text(
"import random\nprint(hasattr(random, 'randint'))\n", encoding="utf-8")
lauf = subprocess.run([sys.executable, "auswertung.py"], cwd=ordner,
capture_output=True, text=True)
print(lauf.stdout.strip().splitlines())['eigene random.py geladen', 'False']
['True']
| Lauf | Ausgabe | Erklärung |
|---|---|---|
mit eigener random.py |
eigene random.py geladen, dann False |
Der Ordner des Skripts steht vorn in sys.path. Python findet die eigene Datei zuerst, und die hat kein randint. |
| ohne eigene Datei | True |
Jetzt wird das Modul der Standardbibliothek gefunden. |
R: find("filter") |
"package:dplyr" "package:stats" |
Beide Pakete haben filter. Gesucht wird in dieser Reihenfolge, das zuletzt geladene steht vorn. |
| R: aktive Fassung | "dplyr" |
Ein blosses filter() meint jetzt die dplyr-Funktion. |
R: stats::filter |
1.5 2.5 3.5 NA | ein gleitender Durchschnitt über zwei Werte, etwas völlig anderes |
R: dplyr::filter |
2 | Zeilen mit x > 2 |
Beim Laden meldet R die Verdeckung mit The following objects are masked from 'package:stats': filter, lag. Diese Sammlung blendet solche Meldungen aus; im eigenen Code sollte man sie lesen.
In einem echten Projekt wäre die Fehlermeldung nicht so freundlich wie hier. Sie lautete etwa module 'random' has no attribute 'randint' und deutete auf alles Mögliche, nur nicht auf die eigene Datei. Die Regel: eigene Dateien nie so benennen wie ein verwendetes Modul, also kein random.py, json.py, numpy.py, pandas.py. In R hilft die ausdrückliche Schreibweise paket::funktion() überall dort, wo zwei geladene Pakete denselben Namen verwenden.
Mit einer eigenen
random.pyim Projektordner lud Python diese Datei statt der Standardbibliothek, undrandintfehlte; nach dem Entfernen war es wieder vorhanden. In R verdeckt dplyr die gleichnamige Funktion aus stats, die mitstats::filterweiterhin erreichbar ist.
Ist Version 2.10.0 neuer als 2.9.0? Und welche der Versionen 2.2.1, 2.2.3, 2.9.0 und 3.0.5 erfüllen die Angaben ==2.2.1, >=2.0, ~=2.2 und <3?
False
True
==2.2.1 ['2.2.1']
>=2.0 ['2.2.1', '2.2.3', '2.9.0', '3.0.5']
~=2.2 ['2.2.1', '2.2.3', '2.9.0']
<3 ['2.2.1', '2.2.3', '2.9.0']
| Vergleich | Python | R | Erklärung |
|---|---|---|---|
"2.10.0" > "2.9.0" |
False |
FALSE |
Zeichen für Zeichen verglichen: 1 kommt vor 9. |
| als Version | True |
TRUE |
Version und package_version vergleichen Zahl für Zahl: 10 > 9. |
| dplyr mindestens 1.1.0 | TRUE |
Der übliche Weg, eine Mindestversion im Code zu prüfen. |
| Angabe | erfüllt von | Erklärung |
|---|---|---|
==2.2.1 |
2.2.1 | genau eine |
>=2.0 |
alle vier | auch 3.0.5, mit möglicherweise brechenden Änderungen |
~=2.2 |
2.2.1, 2.2.3, 2.9.0 | “kompatibel”: mindestens 2.2, aber dieselbe Hauptversion |
<3 |
2.2.1, 2.2.3, 2.9.0 | hier dieselbe Menge wie ~=2.2 |
Wer Versionen als Text vergleicht, hält die neuere für die ältere, sobald eine Stelle zweistellig wird. Die Angabe in der Abhängigkeitsliste ist eine Abwägung: == macht die Auswertung wiederholbar und schliesst Sicherheitsupdates aus, >= lässt alles zu, auch den nächsten grossen Sprung. Für eine abgeschlossene Analyse gehört == hinein, für laufende Arbeit ~=.
Als Zeichenkette gilt
"2.10.0"in beiden Sprachen als kleiner als"2.9.0", als Versionsobjekt korrekt als grösser. Die Angabe~=2.2lässt 2.2.1, 2.2.3 und 2.9.0 zu und schliesst 3.0.5 aus.
Welche Versionen lagen vor, als diese Seite gebaut wurde? Das ist die Information, die an das Ende jedes Berichts gehört.
| Zeile | Bedeutung |
|---|---|
| R-Version | Sprache und Datum der Veröffentlichung |
| Paketversionen in R | benannter Vektor, ein Eintrag je Paket |
| Python-Version | Interpreter, mit dem die Chunks liefen |
paket==version |
genau das Format einer requirements.txt; die Zeilen lassen sich so übernehmen |
Die Zahlen selbst stehen in der Ausgabe und nicht hier im Text, weil sie sich mit jedem Build ändern können. Diese Seite wird mit gespeicherten Ergebnissen gebaut: Die Ausgabe zeigt den Stand beim letzten Neuberechnen der Seite.
Ohne diese Angaben ist eine Auswertung nicht nachprüfbar, denn dieselbe Zeile Code kann mit einer anderen Paketversion eine andere Zahl liefern. Der vollständige Weg ist sessionInfo() in R und pip freeze in Python; für einen Bericht genügen meist die Pakete, die tatsächlich gerechnet haben.
Die Ausgabe dokumentiert die R- und Python-Version sowie die Versionen der verwendeten Pakete zum Zeitpunkt der Berechnung. Im Format
paket==versionlassen sich die Zeilen unmittelbar in einerequirements.txtübernehmen.
Warum gehört .venv in .gitignore, requirements.txt aber ins Repository?
.venv geheime Daten enthält
import sklearn scheitert, pip list zeigt aber scikit-learn. Was ist wahrscheinlich?
pip list zeigt die Distribution korrekt. Die Probe: sys.executable im Skript mit dem pip in der Shell vergleichen.
sklearn ist der richtige Importname.
Ein Skript im Projektordner meldet module 'random' has no attribute 'randint'. Was ist die erste Vermutung?
random.py verdeckt die Standardbibliothek
sys.path, wie Beispiel 2 zeigt. Umbenennen, und einen eventuell entstandenen Ordner __pycache__ löschen.
randint wurde aus Python entfernt
random ist in jeder Umgebung vorhanden.
In einem R-Skript liefert filter(df, x > 2) plötzlich eine Fehlermeldung über eine Zeitreihe. Was ist passiert?
filter meint die Funktion aus stats
library(dplyr) verdeckt dplyr die stats-Funktion; fehlt das Laden, greift die andere. dplyr::filter() ist eindeutig.
filter ist in R veraltet
Eine Abhängigkeitsliste enthält pandas>=2.0. Nach einer Neuinstallation bricht die Auswertung ab. Was hätte ~=2.2 geändert?
~=2.2 erlaubt Fehlerbehebungen innerhalb der Hauptversion 2, aber keinen Sprung auf 3, der nach Konvention Bestehendes ändern darf.
>=2.0 lässt auch 3.0.5 zu, wie Beispiel 3 zeigt.