Python Crash Course for Ecologists

Enough Python to read the code in this course, modify it, and debug it when it breaks. You will not be asked to write programs from scratch.

0. Install everything

Before Day 1 you need a working Python environment. If the word "environment" makes you nervous, read the Python Environments & Conda tutorial first — it explains why we use envs and walks through Miniforge install, activation, and the one-time creation of the course's aiecol env in much more detail than this section. The blocks below are the short version.

macOS

# Install Homebrew if you don't have it.
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

# Install Miniforge (conda + mamba) and a few system tools.
brew install --cask miniforge
brew install git ffmpeg

# Set up conda for your shell (one-time)
conda init zsh
# Close and reopen your terminal.

# Create the course environment (see the environments tutorial for what's happening).
mamba create -n aiecol -c conda-forge python=3.11 sox ffmpeg -y
conda activate aiecol
pip install jupyterlab numpy pandas matplotlib scikit-learn torch torchvision \
            ultralytics opencv-python soundfile librosa

Windows

# Open PowerShell and install via winget.
winget install Git.Git
winget install CondaForge.Miniforge3

# Open a NEW "Miniforge Prompt" (Start Menu) — or restart PowerShell after
# `conda init powershell`. Then:
mamba create -n aiecol -c conda-forge python=3.11 sox ffmpeg -y
conda activate aiecol
pip install jupyterlab numpy pandas matplotlib scikit-learn torch torchvision `
            ultralytics opencv-python soundfile librosa
GPU note: if you have an NVIDIA GPU on Windows or Linux, replace the torch torchvision line with the version from pytorch.org/get-started/locally that matches your CUDA version. On Apple Silicon the default pip install torch uses the GPU automatically through MPS.

1. Running Python

You will use three things:

2. The minimum Python you need

Variables, lists, dicts

# Numbers, strings, and booleans
n_cameras = 24
species = "Odocoileus hemionus"
is_endangered = False

# Lists: ordered, can be modified
sites = ["Crater_Lake_A", "Crater_Lake_B", "Mt_Hood_01"]
sites.append("Mt_Hood_02")
print(sites[0])     # "Crater_Lake_A"
print(len(sites))   # 4

# Dicts: key-value pairs (your most useful data structure)
detection = {
    "image": "IMG_0042.JPG",
    "species": "deer",
    "confidence": 0.87,
    "bbox": [120, 230, 540, 720],   # x1, y1, x2, y2
}
print(detection["species"])         # "deer"

Loops, conditions, functions

for site in sites:
    print(f"Processing {site}")

# A function — wrap any logic you'll use twice
def is_high_confidence(det, threshold=0.7):
    """Return True if the detection is above the confidence threshold."""
    return det["confidence"] >= threshold

if is_high_confidence(detection):
    print("Keep this one")

Reading files

from pathlib import Path

# Walking a directory of camera-trap images
image_dir = Path("/Users/you/oregon_critters/images")
for img_path in image_dir.glob("**/*.JPG"):
    print(img_path.name, img_path.stat().st_size)

3. NumPy — arrays of numbers

NumPy is the foundation of everything else (PyTorch, OpenCV, librosa). Arrays are like R vectors / matrices, but with dimensions explicit.

import numpy as np

counts = np.array([3, 7, 0, 12, 2, 5])
print(counts.mean())   # 4.83
print(counts.sum())    # 29
print(counts[counts > 3])  # [ 7 12  5]

# A grayscale image is just a 2-D array of pixel values 0-255
img = np.zeros((480, 640), dtype=np.uint8)  # all-black 480x640 image
img[100:200, 200:300] = 255                 # paint a white rectangle

4. pandas — tabular data

If you know R's data.frame, you know pandas. A DataFrame is rows and columns with labels.

import pandas as pd

# Read a camera-trap detection CSV
df = pd.read_csv("detections.csv")
print(df.head())
print(df["species"].value_counts())   # how many of each species?

# Filter to high-confidence deer
deer = df[(df["species"] == "deer") & (df["confidence"] > 0.7)]

# Group by site and count
by_site = df.groupby("site").size().reset_index(name="n_detections")
by_site.to_csv("per_site_counts.csv", index=False)

5. matplotlib — plots

import matplotlib.pyplot as plt

fig, ax = plt.subplots(figsize=(6, 4))
ax.bar(by_site["site"], by_site["n_detections"])
ax.set_ylabel("Number of detections")
ax.set_title("Detections per site")
plt.xticks(rotation=45, ha="right")
plt.tight_layout()
plt.savefig("detections_per_site.png", dpi=150)
plt.show()

6. Reading EXIF timestamps from a camera-trap photo

You'll need this in Lab 1 to bin detections by hour of day.

from PIL import Image, ExifTags
from datetime import datetime

def read_timestamp(path):
    """Return a datetime from a camera-trap JPEG's EXIF DateTimeOriginal field."""
    img = Image.open(path)
    exif = {ExifTags.TAGS.get(k): v for k, v in (img._getexif() or {}).items()}
    raw = exif.get("DateTimeOriginal")    # "2023:07:14 06:21:43"
    if raw is None:
        return None
    return datetime.strptime(raw, "%Y:%m:%d %H:%M:%S")

ts = read_timestamp("IMG_0042.JPG")
print(ts.hour)   # e.g. 6 → dawn

7. Common errors and what they mean

ErrorWhat it usually means
ModuleNotFoundError The package isn't installed in this env. Run conda activate aiecol first, then pip install <name>.
IndentationError You mixed tabs and spaces, or your indentation is uneven. Always use 4 spaces.
KeyError: 'species' The dict or DataFrame doesn't have that column. Print the keys/columns first to check.
CUDA out of memory Your GPU ran out of RAM. Lower the batch size, or run with device='cpu'.

8. Where to go for help

→ Continue to Lab 1: Python warm-up on camera-trap metadata