Create your first Python project with pixi
This tutorial builds a weather data analysis tool that reads CSV data, computes statistics with pandas, and produces a chart with matplotlib. Every dependency comes from conda-forge and is managed by pixi.
Prerequisites
Install pixi following the official installation instructions. No separate Python install is required.
Create the project
$ pixi init weather_analysis
✔ Created /path/to/weather_analysis/pixi.toml
$ cd weather_analysis
If you see pixi: command not found, restart your shell so the installer’s PATH update takes effect, or follow the official installation instructions again.
Notice the .gitignore and .gitattributes files pixi created alongside pixi.toml. They come pre-configured so the project-local .pixi/ environment never lands in version control.
pixi.toml stores project metadata, dependencies, and task definitions:
[workspace]
name = "weather_analysis"
channels = ["conda-forge"]
platforms = ["osx-arm64"]The channels field tells pixi where to fetch packages. conda-forge is a community-maintained, openly licensed package repository with thousands of scientific Python packages.
The platforms value reflects your current machine. Pixi sets this automatically.
Note
You may also see a pixi.lock file. This lockfile pins the exact version of every package pixi installs. Commit it to version control so collaborators reproduce the same environment.
Add dependencies
pixi add python pandas matplotlibpixi prints one confirmation line per package as it resolves and installs:
✔ Added python >=3.14.4,<3.15
✔ Added pandas >=3.0.2,<4
✔ Added matplotlib >=3.10.9,<4
If pixi add fails with Failed to fetch repodata, pixi could not reach conda-forge. Check your internet connection and rerun the command.
This does three things:
- Resolves compatible versions of Python, pandas, matplotlib, and all their transitive dependencies from conda-forge.
- Installs everything into a
.pixi/directory inside the project (a project-local environment, not a global one). - Updates
pixi.lockwith the exact versions.
Notice the new .pixi/ directory and pixi.lock file in your project. .pixi/ holds the local environment (Python interpreter, every installed package). pixi.lock records exact resolved versions so a teammate running pixi install against the same lockfile gets the same environment.
The pixi.toml now includes:
[dependencies]
python = ">=3.13.3,<4"
pandas = ">=2.2.3,<3"
matplotlib = ">=3.10.1,<4"The version bounds reflect whichever releases are current when you run the command.
Tip
The .pixi/ directory contains the full environment (Python interpreter, all packages). It can be large. Add .pixi/ to .gitignore and let collaborators recreate it with pixi install.
Create sample data
Create a data directory and add a CSV file:
mkdir dataCreate data/weather.csv with this content:
date,city,temp_high,temp_low,precipitation_mm,humidity_pct
2025-01-01,Portland,8,2,12.5,82
2025-01-02,Portland,7,1,0.0,65
2025-01-03,Portland,9,3,8.3,78
2025-01-04,Portland,6,-1,15.2,88
2025-01-05,Portland,10,4,0.0,60
2025-01-01,Phoenix,18,5,0.0,25
2025-01-02,Phoenix,20,7,0.0,22
2025-01-03,Phoenix,22,8,0.0,20
2025-01-04,Phoenix,19,6,2.1,35
2025-01-05,Phoenix,21,7,0.0,23Write the analysis script
Create analyze.py:
import pandas as pd
import matplotlib
import matplotlib.pyplot as plt
from pathlib import Path
matplotlib.use("Agg")
def load_weather_data(path):
df = pd.read_csv(path, parse_dates=["date"])
df["temp_range"] = df["temp_high"] - df["temp_low"]
return df
def summarize_by_city(df):
return df.groupby("city").agg(
avg_high=("temp_high", "mean"),
avg_low=("temp_low", "mean"),
total_precip=("precipitation_mm", "sum"),
avg_humidity=("humidity_pct", "mean"),
).round(1)
def plot_temperature_comparison(df, output_path):
fig, ax = plt.subplots(figsize=(8, 4))
for city, group in df.groupby("city"):
ax.plot(group["date"], group["temp_high"], marker="o", label=f"{city} high")
ax.plot(group["date"], group["temp_low"], marker="s", label=f"{city} low",
linestyle="--", alpha=0.6)
ax.set_ylabel("Temperature (°C)")
ax.set_title("Daily Temperatures by City")
ax.legend()
fig.tight_layout()
fig.savefig(output_path, dpi=150)
print(f"Chart saved to {output_path}")
plt.close(fig)
def main():
data_dir = Path(__file__).parent / "data"
df = load_weather_data(data_dir / "weather.csv")
summary = summarize_by_city(df)
print("Weather Summary by City:")
print(summary)
print()
plot_temperature_comparison(df, data_dir / "temperatures.png")
if __name__ == "__main__":
main()Run the script
pixi run python analyze.pyExpected output:
Weather Summary by City:
avg_high avg_low total_precip avg_humidity
city
Phoenix 20.0 6.6 2.1 25.0
Portland 8.0 1.8 36.0 74.6
Chart saved to data/temperatures.pngIf you see error: failed to find pyproject.toml or pixi.toml, you ran the command outside the project directory. cd weather_analysis and try again.
Notice the new data/temperatures.png file. Open it to confirm the chart rendered. The matplotlib.use("Agg") line near the top of analyze.py is what tells matplotlib to write the image to disk rather than open a GUI window.
pixi run activates the project environment and executes the command. You never need to activate or deactivate environments manually.
Add a named task
Instead of typing pixi run python analyze.py every time, define a task in pixi.toml:
[tasks]
analyze = "python analyze.py"Now run the analysis with:
pixi run analyzeThe output is identical to running pixi run python analyze.py directly. The named task is just an alias for that command.
Tasks are useful for longer commands, pipelines, and giving collaborators discoverable entry points into the project.
Add a dev dependency and write a test
Add pytest as a dependency:
$ pixi add pytest
✔ Added pytest >=9.0.3,<10
Create test_analyze.py:
import pandas as pd
from analyze import load_weather_data, summarize_by_city
from pathlib import Path
def test_load_weather_data():
path = Path(__file__).parent / "data" / "weather.csv"
df = load_weather_data(path)
assert "temp_range" in df.columns
assert len(df) == 10
def test_summarize_by_city():
df = pd.DataFrame({
"city": ["A", "A", "B"],
"temp_high": [20, 22, 10],
"temp_low": [10, 12, 5],
"precipitation_mm": [0.0, 5.0, 10.0],
"humidity_pct": [50, 60, 70],
})
summary = summarize_by_city(df)
assert summary.loc["A", "avg_high"] == 21.0
assert summary.loc["B", "total_precip"] == 10.0Add a test task and run it:
[tasks]
analyze = "python analyze.py"
test = "pytest"$ pixi run test
============================= test session starts ==============================
platform darwin -- Python 3.14.4, pytest-9.0.3, pluggy-1.6.0
rootdir: /path/to/weather_analysis
collected 2 items
test_analyze.py .. [100%]
============================== 2 passed in 0.33s ===============================
The two dots after test_analyze.py are one dot per passing test. If you see F or E instead, rerun with pixi run pytest -v to see the failing assertion.
Final project structure
-
- pixi.toml
- pixi.lock
- analyze.py
- test_analyze.py
- .gitignore
-
- weather.csv
-
Commit pixi.toml, pixi.lock, your source files, and data. The .pixi/ directory is local and should stay in .gitignore.
Next steps
- Take Over an Existing Conda Environment to learn how conda works when you inherit a project
- pixi reference for a full overview of pixi’s features
- When Should I Choose pixi Over uv? for guidance on picking the right tool
- uv vs pixi vs conda for Scientific Python for a detailed comparison