pfc-cli 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pfc/commands/__init__.py +0 -0
- pfc/commands/barplot.py +64 -0
- pfc/commands/cls.py +18 -0
- pfc/commands/cols.py +21 -0
- pfc/commands/describe.py +152 -0
- pfc/commands/groupby.py +58 -0
- pfc/commands/head.py +11 -0
- pfc/commands/heatmap.py +110 -0
- pfc/commands/help.py +97 -0
- pfc/commands/histplot.py +87 -0
- pfc/commands/index.py +52 -0
- pfc/commands/info.py +61 -0
- pfc/commands/lineplot.py +110 -0
- pfc/commands/load.py +24 -0
- pfc/commands/select.py +37 -0
- pfc/commands/size.py +26 -0
- pfc/commands/tail.py +11 -0
- pfc/commands/unique.py +44 -0
- pfc/commands/value_counts.py +42 -0
- pfc/core/__init__.py +0 -0
- pfc/core/dataset.py +25 -0
- pfc/core/state.py +7 -0
- pfc/main.py +14 -0
- pfc/shell/__init__.py +0 -0
- pfc/shell/parser.py +42 -0
- pfc/shell/repl.py +18 -0
- pfc/ui/__init__.py +0 -0
- pfc/ui/styles.py +0 -0
- pfc/ui/table.py +106 -0
- pfc_cli-0.1.0.dist-info/METADATA +40 -0
- pfc_cli-0.1.0.dist-info/RECORD +35 -0
- pfc_cli-0.1.0.dist-info/WHEEL +5 -0
- pfc_cli-0.1.0.dist-info/entry_points.txt +2 -0
- pfc_cli-0.1.0.dist-info/licenses/LICENSE +0 -0
- pfc_cli-0.1.0.dist-info/top_level.txt +1 -0
pfc/commands/__init__.py
ADDED
|
File without changes
|
pfc/commands/barplot.py
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from pfc.core.state import session
|
|
2
|
+
from rich.console import Console
|
|
3
|
+
from rich.table import Table
|
|
4
|
+
|
|
5
|
+
console = Console()
|
|
6
|
+
|
|
7
|
+
MAX_UNIQUE = 20
|
|
8
|
+
BAR_WIDTH = 25
|
|
9
|
+
BAR_CHAR = "█"
|
|
10
|
+
EMPTY_CHAR = "░"
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def make_bar(value, max_value, width=BAR_WIDTH):
|
|
14
|
+
if max_value == 0:
|
|
15
|
+
return ""
|
|
16
|
+
filled = int((value / max_value) * width)
|
|
17
|
+
return BAR_CHAR * filled + EMPTY_CHAR * (width - filled)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def run(args):
|
|
21
|
+
if session.df is None:
|
|
22
|
+
console.print("[red]No dataset loaded[/red]")
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
if not args:
|
|
26
|
+
console.print("[red]Usage:[/red] barplot <column>")
|
|
27
|
+
return
|
|
28
|
+
|
|
29
|
+
df = session.df.df
|
|
30
|
+
col = " ".join(args)
|
|
31
|
+
|
|
32
|
+
if col not in df.columns:
|
|
33
|
+
console.print(f"[red]Invalid column:[/red] {col}")
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
counts = df[col].value_counts(dropna=False)
|
|
37
|
+
|
|
38
|
+
if len(counts) > MAX_UNIQUE:
|
|
39
|
+
console.print(
|
|
40
|
+
f"[yellow]Cannot plot column '{col}':[/yellow] "
|
|
41
|
+
f"{len(counts)} unique values (limit is {MAX_UNIQUE})"
|
|
42
|
+
)
|
|
43
|
+
console.print("[dim]Tip:[/dim] Use `value_counts` instead.")
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
max_value = counts.max()
|
|
47
|
+
|
|
48
|
+
table = Table(
|
|
49
|
+
title=f"Bar Plot — {col}",
|
|
50
|
+
header_style="bold magenta"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
table.add_column("Value")
|
|
54
|
+
table.add_column("Count", justify="right")
|
|
55
|
+
table.add_column("Bar", justify="left")
|
|
56
|
+
|
|
57
|
+
for value, count in counts.items():
|
|
58
|
+
table.add_row(
|
|
59
|
+
str(value),
|
|
60
|
+
str(count),
|
|
61
|
+
make_bar(count, max_value)
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
console.print(table)
|
pfc/commands/cls.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from rich.console import Console
|
|
3
|
+
|
|
4
|
+
console = Console()
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def run(args):
|
|
8
|
+
# Cross-platform clear
|
|
9
|
+
os.system("cls" if os.name == "nt" else "clear")
|
|
10
|
+
|
|
11
|
+
# Optional: reprint shell header
|
|
12
|
+
console.print("[bold]pfc — Interactive Data Shell[/bold]")
|
|
13
|
+
console.print(
|
|
14
|
+
"Load a dataset once and explore it using built-in commands."
|
|
15
|
+
)
|
|
16
|
+
console.print(
|
|
17
|
+
"Type 'help' to see available commands. Type 'exit' to quit.\n"
|
|
18
|
+
)
|
pfc/commands/cols.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
from pfc.core.state import session
|
|
2
|
+
from rich.console import Console
|
|
3
|
+
from rich.table import Table
|
|
4
|
+
|
|
5
|
+
console = Console()
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def run(args):
|
|
9
|
+
if session.df is None:
|
|
10
|
+
console.print("[red]No dataset loaded[/red]")
|
|
11
|
+
return
|
|
12
|
+
|
|
13
|
+
table = Table(title="Columns", header_style="bold magenta")
|
|
14
|
+
|
|
15
|
+
table.add_column("Index", justify="right")
|
|
16
|
+
table.add_column("Name")
|
|
17
|
+
|
|
18
|
+
for i, col in enumerate(session.df.columns()):
|
|
19
|
+
table.add_row(str(i), col)
|
|
20
|
+
|
|
21
|
+
console.print(table)
|
pfc/commands/describe.py
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# from pfc.core.state import session
|
|
2
|
+
# from rich.console import Console
|
|
3
|
+
# from rich.table import Table
|
|
4
|
+
|
|
5
|
+
# console = Console()
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
# def run(args):
|
|
9
|
+
# if session.df is None:
|
|
10
|
+
# console.print("[red]No dataset loaded[/red]")
|
|
11
|
+
# return
|
|
12
|
+
|
|
13
|
+
# df = session.df.df.describe()
|
|
14
|
+
|
|
15
|
+
# table = Table(
|
|
16
|
+
# title="Dataset Description",
|
|
17
|
+
# header_style="bold magenta",
|
|
18
|
+
# row_styles=["none", "dim"]
|
|
19
|
+
# )
|
|
20
|
+
|
|
21
|
+
# table.add_column("Metric")
|
|
22
|
+
|
|
23
|
+
# for col in df.columns:
|
|
24
|
+
# table.add_column(col, justify="right")
|
|
25
|
+
|
|
26
|
+
# for idx in df.index:
|
|
27
|
+
# table.add_row(idx, *[f"{df[col][idx]:.3f}" for col in df.columns])
|
|
28
|
+
|
|
29
|
+
# console.print(table)
|
|
30
|
+
|
|
31
|
+
# from pfc.core.state import session
|
|
32
|
+
# from rich.console import Console
|
|
33
|
+
# from rich.table import Table
|
|
34
|
+
|
|
35
|
+
# console = Console()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# def run(args):
|
|
39
|
+
# if session.df is None:
|
|
40
|
+
# console.print("[red]No dataset loaded[/red]")
|
|
41
|
+
# return
|
|
42
|
+
|
|
43
|
+
# df = session.df.df
|
|
44
|
+
|
|
45
|
+
# # ---- Select columns ----
|
|
46
|
+
# if args:
|
|
47
|
+
# cols = []
|
|
48
|
+
# for col in args:
|
|
49
|
+
# if col not in df.columns:
|
|
50
|
+
# console.print(f"[red]Invalid column:[/red] {col}")
|
|
51
|
+
# return
|
|
52
|
+
# cols.append(col)
|
|
53
|
+
|
|
54
|
+
# data = df[cols]
|
|
55
|
+
# else:
|
|
56
|
+
# data = df.select_dtypes(include="number")
|
|
57
|
+
|
|
58
|
+
# if data.empty:
|
|
59
|
+
# console.print("[yellow]No numeric columns to describe[/yellow]")
|
|
60
|
+
# return
|
|
61
|
+
|
|
62
|
+
# desc = data.describe()
|
|
63
|
+
|
|
64
|
+
# # ---- Render table ----
|
|
65
|
+
# table = Table(
|
|
66
|
+
# title="Dataset Description",
|
|
67
|
+
# header_style="bold magenta",
|
|
68
|
+
# row_styles=["none", "dim"]
|
|
69
|
+
# )
|
|
70
|
+
|
|
71
|
+
# table.add_column("Metric")
|
|
72
|
+
|
|
73
|
+
# for col in desc.columns:
|
|
74
|
+
# table.add_column(col, justify="right")
|
|
75
|
+
|
|
76
|
+
# for idx in desc.index:
|
|
77
|
+
# table.add_row(
|
|
78
|
+
# idx,
|
|
79
|
+
# *[f"{desc[col][idx]:.3f}" for col in desc.columns]
|
|
80
|
+
# )
|
|
81
|
+
|
|
82
|
+
# console.print(table)
|
|
83
|
+
|
|
84
|
+
from pfc.core.state import session
|
|
85
|
+
from rich.console import Console
|
|
86
|
+
from rich.table import Table
|
|
87
|
+
|
|
88
|
+
console = Console()
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def run(args):
|
|
92
|
+
if session.df is None:
|
|
93
|
+
console.print("[red]No dataset loaded[/red]")
|
|
94
|
+
return
|
|
95
|
+
|
|
96
|
+
df = session.df.df
|
|
97
|
+
|
|
98
|
+
# ---- Select columns ----
|
|
99
|
+
if args:
|
|
100
|
+
cols = []
|
|
101
|
+
non_numeric = []
|
|
102
|
+
|
|
103
|
+
for col in args:
|
|
104
|
+
if col not in df.columns:
|
|
105
|
+
console.print(f"[red]Invalid column:[/red] {col}")
|
|
106
|
+
return
|
|
107
|
+
|
|
108
|
+
if not df[col].dtype.kind in "iufc": # int, unsigned, float, complex
|
|
109
|
+
non_numeric.append(col)
|
|
110
|
+
else:
|
|
111
|
+
cols.append(col)
|
|
112
|
+
|
|
113
|
+
if non_numeric:
|
|
114
|
+
console.print(
|
|
115
|
+
"[yellow]Cannot describe non-numeric column(s):[/yellow] "
|
|
116
|
+
+ ", ".join(non_numeric)
|
|
117
|
+
)
|
|
118
|
+
console.print(
|
|
119
|
+
"[dim]Tip:[/dim] Use `value_counts` or `unique` for categorical columns."
|
|
120
|
+
)
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
data = df[cols]
|
|
124
|
+
|
|
125
|
+
else:
|
|
126
|
+
data = df.select_dtypes(include="number")
|
|
127
|
+
|
|
128
|
+
if data.empty:
|
|
129
|
+
console.print("[yellow]No numeric columns available to describe[/yellow]")
|
|
130
|
+
return
|
|
131
|
+
|
|
132
|
+
desc = data.describe()
|
|
133
|
+
|
|
134
|
+
# ---- Render table ----
|
|
135
|
+
table = Table(
|
|
136
|
+
title="Dataset Description",
|
|
137
|
+
header_style="bold magenta",
|
|
138
|
+
row_styles=["none", "dim"]
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
table.add_column("Metric")
|
|
142
|
+
|
|
143
|
+
for col in desc.columns:
|
|
144
|
+
table.add_column(col, justify="right")
|
|
145
|
+
|
|
146
|
+
for idx in desc.index:
|
|
147
|
+
table.add_row(
|
|
148
|
+
idx,
|
|
149
|
+
*[f"{desc[col][idx]:.3f}" for col in desc.columns]
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
console.print(table)
|
pfc/commands/groupby.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from pfc.core.state import session
|
|
2
|
+
from pfc.ui.table import render
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
|
|
5
|
+
console = Console()
|
|
6
|
+
|
|
7
|
+
ALLOWED_AGGS = {"count", "mean", "sum", "min", "max"}
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def run(args):
|
|
11
|
+
if session.df is None:
|
|
12
|
+
console.print("[red]No dataset loaded[/red]")
|
|
13
|
+
return
|
|
14
|
+
|
|
15
|
+
if len(args) < 3:
|
|
16
|
+
console.print(
|
|
17
|
+
"[red]Usage:[/red] groupby <group_col...> <agg_column> <agg_func>"
|
|
18
|
+
)
|
|
19
|
+
console.print(
|
|
20
|
+
f"[dim]Allowed agg_func:[/dim] {', '.join(ALLOWED_AGGS)}"
|
|
21
|
+
)
|
|
22
|
+
return
|
|
23
|
+
|
|
24
|
+
df = session.df.df
|
|
25
|
+
|
|
26
|
+
agg_func = args[-1]
|
|
27
|
+
agg_col = args[-2]
|
|
28
|
+
group_cols = args[:-2]
|
|
29
|
+
|
|
30
|
+
# ---- Validate agg func ----
|
|
31
|
+
if agg_func not in ALLOWED_AGGS:
|
|
32
|
+
console.print(f"[red]Invalid aggregation function:[/red] {agg_func}")
|
|
33
|
+
return
|
|
34
|
+
|
|
35
|
+
# ---- Validate columns ----
|
|
36
|
+
for col in group_cols + [agg_col]:
|
|
37
|
+
if col not in df.columns:
|
|
38
|
+
console.print(f"[red]Invalid column:[/red] {col}")
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
# ---- Perform groupby ----
|
|
42
|
+
result = (
|
|
43
|
+
df
|
|
44
|
+
.groupby(group_cols)[agg_col]
|
|
45
|
+
.agg(agg_func)
|
|
46
|
+
.reset_index()
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# ---- Rename aggregated column ----
|
|
50
|
+
result.rename(
|
|
51
|
+
columns={agg_col: f"{agg_func.upper()}({agg_col})"},
|
|
52
|
+
inplace=True
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
render(
|
|
56
|
+
result,
|
|
57
|
+
f"{session.file} (groupby {', '.join(group_cols)} → {agg_func} {agg_col})"
|
|
58
|
+
)
|
pfc/commands/head.py
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# pfc/commands/head.py
|
|
2
|
+
from pfc.core.state import session
|
|
3
|
+
from pfc.ui.table import render
|
|
4
|
+
|
|
5
|
+
def run(args):
|
|
6
|
+
if session.df is None:
|
|
7
|
+
print("❌ No dataset loaded")
|
|
8
|
+
return
|
|
9
|
+
|
|
10
|
+
n = int(args[0]) if args else 5
|
|
11
|
+
render(session.df.head(n), f"{session.file} (head {n})")
|
pfc/commands/heatmap.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from pfc.core.state import session
|
|
2
|
+
from rich.console import Console
|
|
3
|
+
from rich.table import Table
|
|
4
|
+
import pandas as pd
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
console = Console()
|
|
8
|
+
|
|
9
|
+
DEFAULT_BINS = 10
|
|
10
|
+
INTENSITY = " .:-=+*#%@"
|
|
11
|
+
|
|
12
|
+
def intensity_char(value, max_value):
|
|
13
|
+
if max_value == 0:
|
|
14
|
+
return " "
|
|
15
|
+
idx = int((value / max_value) * (len(INTENSITY) - 1))
|
|
16
|
+
return INTENSITY[idx]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def run(args):
|
|
20
|
+
if session.df is None:
|
|
21
|
+
console.print("[red]No dataset loaded[/red]")
|
|
22
|
+
return
|
|
23
|
+
|
|
24
|
+
if len(args) < 2:
|
|
25
|
+
console.print(
|
|
26
|
+
"[red]Usage:[/red] heatmap <x_column> <y_column> [value_column] [--bins N]"
|
|
27
|
+
)
|
|
28
|
+
return
|
|
29
|
+
|
|
30
|
+
df = session.df.df.copy()
|
|
31
|
+
|
|
32
|
+
x_col = args[0]
|
|
33
|
+
y_col = args[1]
|
|
34
|
+
value_col = None
|
|
35
|
+
bins = DEFAULT_BINS
|
|
36
|
+
|
|
37
|
+
if "--bins" in args:
|
|
38
|
+
i = args.index("--bins")
|
|
39
|
+
try:
|
|
40
|
+
bins = int(args[i + 1])
|
|
41
|
+
except Exception:
|
|
42
|
+
console.print("[red]Bins must be an integer[/red]")
|
|
43
|
+
return
|
|
44
|
+
args = args[:i]
|
|
45
|
+
|
|
46
|
+
if len(args) == 3:
|
|
47
|
+
value_col = args[2]
|
|
48
|
+
|
|
49
|
+
for col in [x_col, y_col]:
|
|
50
|
+
if col not in df.columns:
|
|
51
|
+
console.print(f"[red]Invalid column:[/red] {col}")
|
|
52
|
+
return
|
|
53
|
+
|
|
54
|
+
if value_col and value_col not in df.columns:
|
|
55
|
+
console.print(f"[red]Invalid value column:[/red] {value_col}")
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
# ---- Bin numeric axes ----
|
|
59
|
+
for col in [x_col, y_col]:
|
|
60
|
+
if df[col].dtype.kind in "iufc":
|
|
61
|
+
df[col] = pd.cut(df[col], bins=bins)
|
|
62
|
+
|
|
63
|
+
# ---- Pivot ----
|
|
64
|
+
if value_col:
|
|
65
|
+
pivot = (
|
|
66
|
+
df.pivot_table(
|
|
67
|
+
index=y_col,
|
|
68
|
+
columns=x_col,
|
|
69
|
+
values=value_col,
|
|
70
|
+
aggfunc="mean",
|
|
71
|
+
observed=True
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
title = f"Heatmap — mean({value_col})"
|
|
75
|
+
else:
|
|
76
|
+
pivot = (
|
|
77
|
+
df.pivot_table(
|
|
78
|
+
index=y_col,
|
|
79
|
+
columns=x_col,
|
|
80
|
+
values=df.columns[0],
|
|
81
|
+
aggfunc="count",
|
|
82
|
+
observed=True
|
|
83
|
+
)
|
|
84
|
+
)
|
|
85
|
+
title = "Heatmap — density"
|
|
86
|
+
|
|
87
|
+
pivot = pivot.fillna(0)
|
|
88
|
+
|
|
89
|
+
max_value = pivot.values.max()
|
|
90
|
+
|
|
91
|
+
# ---- Render ----
|
|
92
|
+
table = Table(
|
|
93
|
+
title=title,
|
|
94
|
+
header_style="bold magenta",
|
|
95
|
+
show_lines=False
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
table.add_column("Y \\ X")
|
|
99
|
+
|
|
100
|
+
for col in pivot.columns:
|
|
101
|
+
table.add_column(str(col)[:6], justify="center")
|
|
102
|
+
|
|
103
|
+
for idx in pivot.index:
|
|
104
|
+
row = [str(idx)[:6]]
|
|
105
|
+
for col in pivot.columns:
|
|
106
|
+
char = intensity_char(pivot.loc[idx, col], max_value)
|
|
107
|
+
row.append(char)
|
|
108
|
+
table.add_row(*row)
|
|
109
|
+
|
|
110
|
+
console.print(table)
|
pfc/commands/help.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
from rich.console import Console
|
|
2
|
+
from rich.table import Table
|
|
3
|
+
|
|
4
|
+
console = Console()
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def section(table, title):
|
|
8
|
+
table.add_row(
|
|
9
|
+
f"[bold magenta]{title}[/bold magenta]",
|
|
10
|
+
""
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def run(args):
|
|
15
|
+
table = Table(
|
|
16
|
+
title="pfc — Command Reference",
|
|
17
|
+
header_style="bold magenta",
|
|
18
|
+
show_lines=False
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
table.add_column("Command", style="cyan", no_wrap=True)
|
|
22
|
+
table.add_column("Description", style="white")
|
|
23
|
+
|
|
24
|
+
# ── Session & Shell ──────────────────────────────
|
|
25
|
+
section(table, "Session & Shell")
|
|
26
|
+
table.add_row("help", "Show this help summary")
|
|
27
|
+
table.add_row("cls", "Clear the terminal screen (keeps dataset loaded)")
|
|
28
|
+
table.add_row("exit", "Exit the interactive shell")
|
|
29
|
+
|
|
30
|
+
table.add_row("", "") # spacer
|
|
31
|
+
|
|
32
|
+
# ── Data Loading ─────────────────────────────────
|
|
33
|
+
section(table, "Data Loading")
|
|
34
|
+
table.add_row("load <file.csv>", "Load a CSV file into the current session")
|
|
35
|
+
|
|
36
|
+
table.add_row("", "")
|
|
37
|
+
|
|
38
|
+
# ── Data Structure & Metadata ────────────────────
|
|
39
|
+
section(table, "Data Structure & Metadata")
|
|
40
|
+
table.add_row("cols", "List all column names")
|
|
41
|
+
table.add_row("index", "Show dataset index range and preview")
|
|
42
|
+
table.add_row("size", "Show dataset dimensions (rows × columns)")
|
|
43
|
+
table.add_row("info", "Show dataset metadata and column information")
|
|
44
|
+
|
|
45
|
+
table.add_row("", "")
|
|
46
|
+
|
|
47
|
+
# ── Data Viewing ─────────────────────────────────
|
|
48
|
+
section(table, "Data Viewing")
|
|
49
|
+
table.add_row("head [n]", "Display the first n rows of the dataset (default: 5)")
|
|
50
|
+
table.add_row("tail [n]", "Display the last n rows of the dataset (default: 5)")
|
|
51
|
+
|
|
52
|
+
table.add_row("", "")
|
|
53
|
+
|
|
54
|
+
# ── Column Operations ────────────────────────────
|
|
55
|
+
section(table, "Column Operations")
|
|
56
|
+
table.add_row("select <col1> <col2> ...", "Display only the selected columns")
|
|
57
|
+
table.add_row(
|
|
58
|
+
"unique <column> [--count]",
|
|
59
|
+
"List unique values of a column (optionally with counts)",
|
|
60
|
+
)
|
|
61
|
+
table.add_row(
|
|
62
|
+
"value_counts <column> [--normalize]",
|
|
63
|
+
"Show frequency distribution of a column",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
table.add_row("", "")
|
|
67
|
+
|
|
68
|
+
# ── Aggregation & Statistics ─────────────────────
|
|
69
|
+
section(table, "Aggregation & Statistics")
|
|
70
|
+
table.add_row("describe [col...]", "Statistical summary of numeric columns")
|
|
71
|
+
table.add_row(
|
|
72
|
+
"groupby <group_col...> <agg_column> <agg_func>",
|
|
73
|
+
"Group data and aggregate (count, mean, sum, min, max)",
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
table.add_row("", "")
|
|
77
|
+
|
|
78
|
+
# ── Visualization ────────────────────────────────
|
|
79
|
+
section(table, "Visualization")
|
|
80
|
+
table.add_row(
|
|
81
|
+
"barplot <column>",
|
|
82
|
+
"Bar plot for categorical columns (≤ 20 unique values)",
|
|
83
|
+
)
|
|
84
|
+
table.add_row(
|
|
85
|
+
"hist <column> [bins]",
|
|
86
|
+
"Histogram for a numeric column (default bins: 10)",
|
|
87
|
+
)
|
|
88
|
+
table.add_row(
|
|
89
|
+
"lineplot <x_column> <y_column> [--agg mean|sum|min|max|count]",
|
|
90
|
+
"Line plot of aggregated y-values against x-values",
|
|
91
|
+
)
|
|
92
|
+
table.add_row(
|
|
93
|
+
"heatmap <x_column> <y_column> [value_column] [--bins N]",
|
|
94
|
+
"Heatmap showing density or aggregated values",
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
console.print(table)
|
pfc/commands/histplot.py
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
from pfc.core.state import session
|
|
2
|
+
from rich.console import Console
|
|
3
|
+
from rich.table import Table
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
console = Console()
|
|
7
|
+
|
|
8
|
+
DEFAULT_BINS = 10
|
|
9
|
+
BAR_WIDTH = 25
|
|
10
|
+
BAR_CHAR = "█"
|
|
11
|
+
EMPTY_CHAR = "░"
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def make_bar(value, max_value, width=BAR_WIDTH):
|
|
15
|
+
if max_value == 0:
|
|
16
|
+
return ""
|
|
17
|
+
filled = int((value / max_value) * width)
|
|
18
|
+
return BAR_CHAR * filled + EMPTY_CHAR * (width - filled)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def run(args):
|
|
22
|
+
if session.df is None:
|
|
23
|
+
console.print("[red]No dataset loaded[/red]")
|
|
24
|
+
return
|
|
25
|
+
|
|
26
|
+
if not args:
|
|
27
|
+
console.print("[red]Usage:[/red] hist <numeric_column> [bins]")
|
|
28
|
+
return
|
|
29
|
+
|
|
30
|
+
df = session.df.df
|
|
31
|
+
col = args[0]
|
|
32
|
+
|
|
33
|
+
# ---- Validate column ----
|
|
34
|
+
if col not in df.columns:
|
|
35
|
+
console.print(f"[red]Invalid column:[/red] {col}")
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
if not df[col].dtype.kind in "iufc":
|
|
39
|
+
console.print(
|
|
40
|
+
f"[yellow]Column '{col}' is not numeric[/yellow]"
|
|
41
|
+
)
|
|
42
|
+
console.print("[dim]Tip:[/dim] Use barplot / value_counts for categorical data.")
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
# ---- Bins ----
|
|
46
|
+
bins = DEFAULT_BINS
|
|
47
|
+
if len(args) > 1:
|
|
48
|
+
try:
|
|
49
|
+
bins = int(args[1])
|
|
50
|
+
if bins <= 0:
|
|
51
|
+
raise ValueError
|
|
52
|
+
except ValueError:
|
|
53
|
+
console.print("[red]Bins must be a positive integer[/red]")
|
|
54
|
+
return
|
|
55
|
+
|
|
56
|
+
data = df[col].dropna().values
|
|
57
|
+
|
|
58
|
+
if len(data) == 0:
|
|
59
|
+
console.print("[yellow]No data available for histogram[/yellow]")
|
|
60
|
+
return
|
|
61
|
+
|
|
62
|
+
# ---- Compute histogram ----
|
|
63
|
+
counts, edges = np.histogram(data, bins=bins)
|
|
64
|
+
max_count = counts.max()
|
|
65
|
+
|
|
66
|
+
# ---- Build table ----
|
|
67
|
+
table = Table(
|
|
68
|
+
title=f"Histogram — {col} ({bins} bins)",
|
|
69
|
+
header_style="bold magenta"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
table.add_column("Range")
|
|
73
|
+
table.add_column("Count", justify="right")
|
|
74
|
+
table.add_column("Bar")
|
|
75
|
+
|
|
76
|
+
for i in range(len(counts)):
|
|
77
|
+
left = edges[i]
|
|
78
|
+
right = edges[i + 1]
|
|
79
|
+
label = f"{left:.2f} – {right:.2f}"
|
|
80
|
+
|
|
81
|
+
table.add_row(
|
|
82
|
+
label,
|
|
83
|
+
str(counts[i]),
|
|
84
|
+
make_bar(counts[i], max_count)
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
console.print(table)
|
pfc/commands/index.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from pfc.core.state import session
|
|
2
|
+
from rich.console import Console
|
|
3
|
+
from rich.table import Table
|
|
4
|
+
|
|
5
|
+
console = Console()
|
|
6
|
+
|
|
7
|
+
PREVIEW_COUNT = 10
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def run(args):
|
|
11
|
+
if session.df is None:
|
|
12
|
+
console.print("[red]No dataset loaded[/red]")
|
|
13
|
+
return
|
|
14
|
+
|
|
15
|
+
df = session.df.df # pandas DataFrame
|
|
16
|
+
index = df.index
|
|
17
|
+
|
|
18
|
+
start_idx = index[0]
|
|
19
|
+
end_idx = index[-1]
|
|
20
|
+
total = len(index)
|
|
21
|
+
|
|
22
|
+
# ---- Summary table ----
|
|
23
|
+
summary = Table(
|
|
24
|
+
title="Index Summary",
|
|
25
|
+
header_style="bold magenta"
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
summary.add_column("Start Index", justify="right")
|
|
29
|
+
summary.add_column("End Index", justify="right")
|
|
30
|
+
summary.add_column("Total Rows", justify="right")
|
|
31
|
+
|
|
32
|
+
summary.add_row(
|
|
33
|
+
str(start_idx),
|
|
34
|
+
str(end_idx),
|
|
35
|
+
str(total)
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
console.print(summary)
|
|
39
|
+
|
|
40
|
+
# ---- Preview table ----
|
|
41
|
+
preview = Table(
|
|
42
|
+
title=f"First {min(PREVIEW_COUNT, total)} Index Values",
|
|
43
|
+
header_style="bold magenta"
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
preview.add_column("Position", justify="right")
|
|
47
|
+
preview.add_column("Index Value", justify="right")
|
|
48
|
+
|
|
49
|
+
for i, idx in enumerate(index[:PREVIEW_COUNT]):
|
|
50
|
+
preview.add_row(str(i), str(idx))
|
|
51
|
+
|
|
52
|
+
console.print(preview)
|