# Getting started

## Installation

Install from PyPI:

```bash
pip install walkingpandas
```

With optional extras:

=== "Development"

    ```bash
    pip install walkingpandas[dev]
    ```

=== "Map matching"

    ```bash
    pip install walkingpandas[mapmatch]
    ```

=== "NetworkX graphs"

    ```bash
    pip install walkingpandas[networkx]
    ```

=== "OpenStreetMap (osmnx)"

    ```bash
    pip install walkingpandas[osmnx]
    ```

=== "Documentation"

    ```bash
    pip install walkingpandas[docs]
    ```

## Quick start

The typical flow is: create a **Network** (topology), create a **WalkFrame** (walks + network), then chain filters/aggregations and call `.compute()`.

```python
import walkingpandas as wp

# 1. Network (static topology)
network = wp.Network(
    nodes="data/network/nodes.parquet",
    edges="data/network/edges.parquet",
)

# 2. WalkFrame -- use the factory that matches your data
walks = wp.WalkFrame.from_parquet("data/walks/*.parquet", network=network)

# 3. Lazy query chain; nothing runs until .compute()
result = (
    walks
    .only_simple_paths()
    .passing_through([42])
    .filter(time_range=("08:00", "09:00"))
    .edge_frequencies()
    .compute()
)
```

If you have nodes, edges, and walks all as separate Parquet paths, use the one-liner:

```python
walks = wp.read_dataset(
    nodes="data/network/nodes.parquet",
    edges="data/network/edges.parquet",
    walks="data/walks/*.parquet",
)
traffic = walks.edge_frequencies().compute()
```

## Creating a WalkFrame (by data source)

Choose the entry point that matches what you have. You always need **either** an existing `Network` **or** `nodes` (and optionally `edges`).

| Your situation | Recommended API | Example |
|----------------|-----------------|---------|
| Parquet walks + network (or nodes/edges) | `WalkFrame.from_parquet(...)` or `read_dataset(...)` | `WalkFrame.from_parquet("walks/*.parquet", network=net)` |
| In-memory DataFrame of walks | `WalkFrame.from_dataframe(...)` | `WalkFrame.from_dataframe(walks_df, nodes=nodes_gdf)` |
| Only Parquet walks (infer network) | `WalkFrame.from_walks(..., infer_network=True)` | `WalkFrame.from_walks("walks/*.parquet", infer_network=True)` |
| Single DuckDB file (network + walks) | `connect(...)` or `WalkFrame.from_duckdb(...)` | `connect("data.duckdb", walks_table="wp_walks")` |
| Network in DuckDB, walks in Parquet | `connect(...)` | `connect("net.duckdb", walks_path="walks/*.parquet")` |

!!! note
    The constructor `WalkFrame(parquet_path, network=...)` is still valid; the `from_*` methods are named alternatives that make the data source explicit. For DuckDB-based loading, `connect()` is the single top-level function; `read_duckdb` is deprecated.

## Network persistence (save / load)

Save and reload the Network for faster restarts:

```python
network = wp.Network(nodes=nodes_gdf, edges=edges_gdf)
network.save("my_network.duckdb")

# Later: load network, then attach walks
network = wp.Network.load("my_network.duckdb")
walks = wp.WalkFrame.from_parquet("data/walks/*.parquet", network=network)

# Or in one call:
walks = wp.connect("my_network.duckdb", walks_path="data/walks/*.parquet")
```

You can also create a single DuckDB file that contains both network and walks:

```python
wp.write_duckdb(network, walks_df, "data.duckdb")
walks = wp.connect("data.duckdb", walks_table="wp_walks")
```

## How the API fits together

1. **Network** -- defines the topology (nodes, edges, optional geometry). Create from Parquet, GeoDataFrames, NetworkX, or load from DuckDB.
2. **WalkFrame** -- needs a walks source and a network. Use the creation method that matches your data.
3. **Query** -- all WalkFrames share the same lazy methods: `filter()`, `passing_through()`, `only_simple_paths()`, `edge_frequencies()`, etc. Chain them; nothing runs until `.compute()`.
4. **Result** -- `compute()` returns a pandas DataFrame (or `to_geopandas()` when nodes carry geometry).
