# phrs — the tabular data shell tool
`phrs` is a Rust command-line tool for manipulating CSV data in Unix pipelines. It is inspired by [ph](https://github.com/pgdr/ph) and uses [Polars](https://pola.rs/) for tabular operations.
## Install
```bash
cargo install phrs
```
## Getting started
Suppose `a.csv` contains:
```csv
x,y
3,8
4,9
5,10
6,11
7,12
8,13
```
Commands usually read CSV from standard input and write CSV to standard output, so they can be chained:
```bash
```csv
y,x
8,3
9,4
10,5
```
To display a human-readable table, use `show`:
```bash
```text
x y
-- --- ---
0 3 8
1 4 9
2 5 10
3 6 11
4 7 12
5 8 13
```
`show` produces terminal output rather than CSV; it is normally the last command in a pipeline.
## Command help
`help` lists all implemented commands and gives usage details for a specific
command. It does not read standard input.
```bash
phrs help
phrs help round
phrs help describe
```
## Commands
### Inspect and select columns
```bash
cat a.csv | phrs shape # Output row and column counts as CSV
```
### Select and order rows
```bash
cat a.csv | phrs tail # Last 10 rows (default)
cat a.csv | phrs tail 3
cat a.csv | phrs sort x # Sort by column x
cat a.csv | phrs slice :3 # First three rows
cat a.csv | phrs slice 1:9:2 # Every second row from index 1
cat a.csv | phrs slice ::-1 # Reverse the rows
```
### Rename and clean columns
```bash
cat a.csv | phrs strip # Trim whitespace in string columns
cat a.csv | phrs strip x # Trim a specified column
```
### Filter rows with `query`
`query` supports simple column-to-value comparisons (`==`, `!=`, `<`, `<=`, `>`, `>=`):
```bash
```
### Compute columns with `eval`
`eval` supports arithmetic expressions, assignments, numeric constants, parentheses, and exponentiation:
```bash
cat a.csv | phrs eval 'z = (x + y) / 2'
cat a.csv | phrs eval 'x**2' # Output only the computed column
```
### Round numerical columns with `round`
`round COLUMN [DECIMALS]` rounds the selected numeric column; the default
precision is zero decimal places. You can also write `--decimals=N`. Negative
precisions round to tens, hundreds, etc. Halfway values round to the nearest
even multiple (the same convention as pandas). Other columns, and missing
values, are preserved. Integer columns use exact integer arithmetic.
```bash
cat a.csv | phrs round x --decimals=2
cat a.csv | phrs round x --decimals=-1
```
### Compute differences with `diff`
`diff` subtracts the previous row from the current row. By default it processes all columns; specify column names to limit the operation. Use `--periods` to change the offset or `--axis=1` to compute differences across columns.
```bash
cat a.csv | phrs diff --axis=1
```
### Aggregate columns with `sum`, `mean`, `median`, `std`, `min`, `max`
Each command reduces the input CSV to a **single-row CSV**, preserving the
original column headers. By default reductions are column-wise (`--axis=0`).
Select particular columns by name, or pass `--axis=1` to aggregate across
columns and produce a single row indexed by the original row numbers.
```bash
cat a.csv | phrs median
cat a.csv | phrs std # Sample standard deviation (ddof=1)
cat a.csv | phrs min
cat a.csv | phrs max
cat a.csv | phrs sum x # Reduce only column x
cat a.csv | phrs mean --axis=1 # Mean of each input row
cat a.csv | phrs std --ddof=0 # Population standard deviation
cat a.csv | phrs mean --numeric_only=True
cat a.csv | phrs sum --min_count=2
cat a.csv | phrs mean --skipna=False
```
Missing numeric values are excluded by default; `--skipna=False` propagates
missing values. `sum` defaults to zero for an entirely missing column (unless
`--min_count` requests more observations). For mixed numeric/text data,
`sum` concatenates text and `min` / `max` compare strings. `mean`, `median`
and `std` require numeric columns unless `--numeric_only=True` is supplied.
### Handle missing values with `dropna` and `fillna`
`dropna` removes rows containing missing cells (`--axis=0`, the default),
whereas `--axis=1` removes columns. By default, any missing cell causes the
row/column to be removed; `--how=all` removes only entirely missing rows/columns.
`--thresh=N` instead retains rows/columns containing at least N nonmissing
cells. Missing values include CSV blanks, NaNs, and pandas' standard NA strings.
```bash
cat a.csv | phrs dropna --thresh=5
cat a.csv | phrs dropna --axis=1
```
`fillna` replaces missing cells either with a scalar or using the closest
preceding/following nonmissing value in the same column. Use `--method=pad` or
`--method=ffill` for forward filling, and `--method=bfill` or
`--method=backfill` for backward filling. A method and a scalar value are
mutually exclusive.
```bash
cat a.csv | phrs fillna missing
cat a.csv | phrs fillna --method=pad
cat a.csv | phrs fillna --method=bfill --limit=7
cat a.csv | phrs fillna 0 --limit=2
```
For a **method**, `--limit` caps the number of consecutive gaps filled per
run. For a **scalar**, it caps the total number of missing values filled per
column, following pandas' behavior.
### Compute rolling statistics with `rolling`
`rolling WINDOW [COLUMN ...]` computes a trailing window reduction. Its
`--how` option accepts `sum` (the default), `mean`, `median`, `std`, `min`, and
`max`. It returns a CSV with the **same number of rows**. Results are
floating-point, and incomplete windows are blank by default.
```bash
cat a.csv | phrs rolling 3 --how=median
cat a.csv | phrs rolling 5 --how=std --ddof=0
cat a.csv | phrs rolling 7 x y --how=mean # Preserve unselected columns
cat a.csv | phrs rolling 3 --how=max --min_periods=1
cat a.csv | phrs rolling 3 --how=mean --center=True
```
When specific column names are supplied, only those columns are replaced;
other columns (such as timestamps) retain their original values and order.
Without column names, rolling aggregates all numeric columns and excludes
nonnumeric columns. `--min_periods` controls the minimum number of nonmissing
observations (default: the window size). `--center=True` centers the output
within each window. `std` uses sample standard deviation (`ddof=1`) unless
another `--ddof` is supplied.
### Parse dates with `date`
`date` converts a column to ISO-formatted dates. Numeric values are interpreted as days since the Unix epoch, or as Unix timestamps in seconds with `--utc=True`. String dates can use an explicit format or day-first parsing.
```bash
cat dates.csv | phrs date recorded --dayfirst=True
cat timestamps.csv | phrs date timestamp --utc=True
```
### Display a table
```bash
```
### Open Excel workbooks
`open excel` reads the first worksheet of an `.xls`, `.xlsx`, `.xlsm`, or
`.xlsb` file and writes CSV to standard output. Use `--sheet=NAME` to select
another worksheet, or `--sheet_name=N` to select a zero-based worksheet index
(as in `ph` and pandas). `--sheet_name=NAME` also accepts worksheet names.
Unlike ordinary commands, it reads the named file, not stdin.
```bash
phrs open excel data.xls
phrs open excel data.xlsx --sheet=Measurements
phrs open excel data.xlsx --sheet_name=1 # Second worksheet
Cells are exported as values: dates use ISO notation, blank cells become empty
fields, and commas, quotes, and newlines are CSV-escaped. Excel cell formatting
(such as currency symbols or formatted leading zeros) is not reproduced.
Formulas are **not evaluated**; their cached results, if available, are exported.
### Concatenate CSV files
Unlike most commands, `cat` takes filenames. With no filenames, it reads standard input.
```bash
phrs cat a.csv b.csv
phrs cat a.csv b.csv --axis=index
phrs cat a.csv b.csv --axis=columns
### Merge CSV files
`merge` reads two named CSV files and joins their rows. The default join type is `inner`; supported types are `inner`, `left`, `right`, and `outer`.
```bash
phrs merge left.csv right.csv
phrs merge left.csv right.csv --how=outer
phrs merge left.csv right.csv --on=key
phrs merge left.csv right.csv --left=left_key --right=right_key
```
When no join key is provided, common column names are used as join keys.
## Pipelines
```bash
cat a.csv \
| phrs query 'x>4' \
| phrs eval 'z = x * y' \
| phrs columns x z \
| phrs sort x \
| phrs show
```
## Development
```bash
cargo test
cargo build --release
```
The optimized executable is written to `target/release/phrs`.
`phrs` aims to reproduce the behavior of the Python [`ph`](https://github.com/pgdr/ph) tool for its implemented commands, but does not yet support every `ph` operation. Differences are tracked using differential tests against the Python implementation.
## License
MIT.