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.
File without changes
@@ -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)
@@ -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)
@@ -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})")
@@ -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)
@@ -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)