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/info.py
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
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 # pandas DataFrame
|
|
14
|
+
|
|
15
|
+
rows, cols = df.shape
|
|
16
|
+
mem_bytes = df.memory_usage(deep=True).sum()
|
|
17
|
+
mem_mb = mem_bytes / (1024 * 1024)
|
|
18
|
+
|
|
19
|
+
# ---------------- Summary ----------------
|
|
20
|
+
summary = Table(
|
|
21
|
+
title="Dataset Info",
|
|
22
|
+
header_style="bold magenta"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
summary.add_column("Property")
|
|
26
|
+
summary.add_column("Value", justify="right")
|
|
27
|
+
|
|
28
|
+
summary.add_row("File", session.file)
|
|
29
|
+
summary.add_row("Rows", str(rows))
|
|
30
|
+
summary.add_row("Columns", str(cols))
|
|
31
|
+
summary.add_row("Memory Usage", f"{mem_mb:.2f} MB")
|
|
32
|
+
|
|
33
|
+
console.print(summary)
|
|
34
|
+
|
|
35
|
+
# ---------------- Column Info ----------------
|
|
36
|
+
col_table = Table(
|
|
37
|
+
title="Column Information",
|
|
38
|
+
header_style="bold magenta"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
col_table.add_column("Column")
|
|
42
|
+
col_table.add_column("Dtype")
|
|
43
|
+
col_table.add_column("Non-Null", justify="right")
|
|
44
|
+
col_table.add_column("Nulls", justify="right")
|
|
45
|
+
col_table.add_column("Null %", justify="right")
|
|
46
|
+
|
|
47
|
+
for col in df.columns:
|
|
48
|
+
total = len(df)
|
|
49
|
+
non_null = df[col].count()
|
|
50
|
+
nulls = total - non_null
|
|
51
|
+
null_pct = (nulls / total) * 100 if total else 0
|
|
52
|
+
|
|
53
|
+
col_table.add_row(
|
|
54
|
+
col,
|
|
55
|
+
str(df[col].dtype),
|
|
56
|
+
str(non_null),
|
|
57
|
+
str(nulls),
|
|
58
|
+
f"{null_pct:.2f}%"
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
console.print(col_table)
|
pfc/commands/lineplot.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
|
+
|
|
6
|
+
console = Console()
|
|
7
|
+
|
|
8
|
+
MAX_RENDER_POINTS = 50
|
|
9
|
+
HEIGHT = 10
|
|
10
|
+
POINT_CHAR = "●"
|
|
11
|
+
EMPTY_CHAR = " "
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def run(args):
|
|
15
|
+
if session.df is None:
|
|
16
|
+
console.print("[red]No dataset loaded[/red]")
|
|
17
|
+
return
|
|
18
|
+
|
|
19
|
+
if len(args) < 2:
|
|
20
|
+
console.print("[red]Usage:[/red] lineplot <x_column> <y_column> [--agg mean|sum|min|max|count]")
|
|
21
|
+
return
|
|
22
|
+
|
|
23
|
+
df = session.df.df
|
|
24
|
+
|
|
25
|
+
x_col = args[0]
|
|
26
|
+
y_col = args[1]
|
|
27
|
+
|
|
28
|
+
agg = "mean"
|
|
29
|
+
if "--agg" in args:
|
|
30
|
+
idx = args.index("--agg")
|
|
31
|
+
try:
|
|
32
|
+
agg = args[idx + 1]
|
|
33
|
+
except IndexError:
|
|
34
|
+
console.print("[red]Missing aggregation after --agg[/red]")
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
if x_col not in df.columns:
|
|
38
|
+
console.print(f"[red]Invalid x column:[/red] {x_col}")
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
if y_col not in df.columns:
|
|
42
|
+
console.print(f"[red]Invalid y column:[/red] {y_col}")
|
|
43
|
+
return
|
|
44
|
+
|
|
45
|
+
if not df[y_col].dtype.kind in "iufc":
|
|
46
|
+
console.print(f"[red]Y column must be numeric:[/red] {y_col}")
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
if agg not in {"mean", "sum", "min", "max", "count"}:
|
|
50
|
+
console.print("[red]Invalid aggregation[/red]")
|
|
51
|
+
return
|
|
52
|
+
|
|
53
|
+
# ---- Aggregate ----
|
|
54
|
+
grouped = (
|
|
55
|
+
df.groupby(x_col)[y_col]
|
|
56
|
+
.agg(agg)
|
|
57
|
+
.reset_index()
|
|
58
|
+
.sort_values(x_col)
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
n_points = len(grouped)
|
|
62
|
+
step = 1
|
|
63
|
+
|
|
64
|
+
if n_points > MAX_RENDER_POINTS:
|
|
65
|
+
step = (n_points + MAX_RENDER_POINTS - 1) // MAX_RENDER_POINTS
|
|
66
|
+
grouped = grouped.iloc[::step].reset_index(drop=True)
|
|
67
|
+
|
|
68
|
+
console.print(
|
|
69
|
+
f"[dim]Downsampling applied:[/dim] showing every {step} point "
|
|
70
|
+
f"({len(grouped)} of {n_points})"
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
y_values = grouped[y_col].values
|
|
75
|
+
y_min, y_max = y_values.min(), y_values.max()
|
|
76
|
+
|
|
77
|
+
# Normalize Y to HEIGHT
|
|
78
|
+
def scale(y):
|
|
79
|
+
if y_max == y_min:
|
|
80
|
+
return HEIGHT // 2
|
|
81
|
+
return int((y - y_min) / (y_max - y_min) * (HEIGHT - 1))
|
|
82
|
+
|
|
83
|
+
scaled = [scale(y) for y in y_values]
|
|
84
|
+
|
|
85
|
+
# ---- Build plot grid ----
|
|
86
|
+
grid = [[EMPTY_CHAR] * len(scaled) for _ in range(HEIGHT)]
|
|
87
|
+
|
|
88
|
+
for x, y in enumerate(scaled):
|
|
89
|
+
grid[HEIGHT - 1 - y][x] = POINT_CHAR
|
|
90
|
+
|
|
91
|
+
# ---- Render ----
|
|
92
|
+
table = Table(
|
|
93
|
+
title=f"Line Plot — {y_col} vs {x_col} ({agg})",
|
|
94
|
+
header_style="bold magenta"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
table.add_column("Y")
|
|
98
|
+
table.add_column("Plot")
|
|
99
|
+
|
|
100
|
+
for i, row in enumerate(grid):
|
|
101
|
+
y_label = f"{y_max - i * (y_max - y_min) / (HEIGHT - 1):.2f}"
|
|
102
|
+
table.add_row(y_label, "".join(row))
|
|
103
|
+
|
|
104
|
+
table.add_row("", "-" * len(scaled))
|
|
105
|
+
table.add_row("X", " ".join(str(x)[:1] for x in grouped[x_col]))
|
|
106
|
+
|
|
107
|
+
console.print(table)
|
|
108
|
+
console.print(
|
|
109
|
+
"[dim]Note:[/dim] X-axis is sampled; gaps reflect uneven data distribution."
|
|
110
|
+
)
|
pfc/commands/load.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from pfc.core.state import session
|
|
2
|
+
from pfc.core.dataset import Dataset
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
|
|
5
|
+
console = Console()
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def run(args):
|
|
9
|
+
if not args:
|
|
10
|
+
console.print("[red]Usage:[/red] load <file.csv>")
|
|
11
|
+
return
|
|
12
|
+
|
|
13
|
+
path = args[0]
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
dataset = Dataset(path)
|
|
17
|
+
except Exception as e:
|
|
18
|
+
console.print(f"[red]Failed to load CSV:[/red] {e}")
|
|
19
|
+
return
|
|
20
|
+
|
|
21
|
+
session.df = dataset
|
|
22
|
+
session.file = path
|
|
23
|
+
|
|
24
|
+
console.print(f"[bold green]Loaded[/bold green] {path}")
|
pfc/commands/select.py
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
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
|
+
|
|
8
|
+
def run(args):
|
|
9
|
+
if session.df is None:
|
|
10
|
+
console.print("[red]No dataset loaded[/red]")
|
|
11
|
+
return
|
|
12
|
+
|
|
13
|
+
if not args:
|
|
14
|
+
console.print("[red]Usage:[/red] select <col1> <col2> ...")
|
|
15
|
+
return
|
|
16
|
+
|
|
17
|
+
df = session.df.df # pandas DataFrame
|
|
18
|
+
available_cols = list(df.columns)
|
|
19
|
+
|
|
20
|
+
# Handle column names with spaces (already split by shell)
|
|
21
|
+
selected_cols = args
|
|
22
|
+
|
|
23
|
+
# Validate columns
|
|
24
|
+
invalid = [c for c in selected_cols if c not in available_cols]
|
|
25
|
+
if invalid:
|
|
26
|
+
console.print(
|
|
27
|
+
f"[red]Invalid column(s):[/red] {', '.join(invalid)}"
|
|
28
|
+
)
|
|
29
|
+
console.print(
|
|
30
|
+
f"[dim]Available columns:[/dim] {', '.join(available_cols)}"
|
|
31
|
+
)
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
# Select columns (NON-MUTATING)
|
|
35
|
+
result = df[selected_cols].copy()
|
|
36
|
+
|
|
37
|
+
render(result, f"{session.file} (select: {', '.join(selected_cols)})")
|
pfc/commands/size.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
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 # pandas DataFrame
|
|
14
|
+
|
|
15
|
+
rows, cols = df.shape
|
|
16
|
+
total = rows * cols
|
|
17
|
+
|
|
18
|
+
table = Table(title="Dataset Size", header_style="bold magenta")
|
|
19
|
+
|
|
20
|
+
table.add_column("Rows", justify="right")
|
|
21
|
+
table.add_column("Columns", justify="right")
|
|
22
|
+
table.add_column("Total Cells", justify="right")
|
|
23
|
+
|
|
24
|
+
table.add_row(str(rows), str(cols), str(total))
|
|
25
|
+
|
|
26
|
+
console.print(table)
|
pfc/commands/tail.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.tail(n), f"{session.file} (head {n})")
|
pfc/commands/unique.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from pfc.core.state import session
|
|
2
|
+
from pfc.ui.table import render
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
import pandas as pd
|
|
5
|
+
|
|
6
|
+
console = Console()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def run(args):
|
|
10
|
+
if session.df is None:
|
|
11
|
+
console.print("[red]No dataset loaded[/red]")
|
|
12
|
+
return
|
|
13
|
+
|
|
14
|
+
if not args:
|
|
15
|
+
console.print("[red]Usage:[/red] unique <column> [--count]")
|
|
16
|
+
return
|
|
17
|
+
|
|
18
|
+
df = session.df.df
|
|
19
|
+
|
|
20
|
+
show_count = False
|
|
21
|
+
if "--count" in args:
|
|
22
|
+
show_count = True
|
|
23
|
+
args.remove("--count")
|
|
24
|
+
|
|
25
|
+
col = " ".join(args)
|
|
26
|
+
|
|
27
|
+
if col not in df.columns:
|
|
28
|
+
console.print(f"[red]Invalid column:[/red] {col}")
|
|
29
|
+
console.print(f"[dim]Available columns:[/dim] {', '.join(df.columns)}")
|
|
30
|
+
return
|
|
31
|
+
|
|
32
|
+
if show_count:
|
|
33
|
+
vc = df[col].value_counts(dropna=False)
|
|
34
|
+
result = pd.DataFrame({
|
|
35
|
+
col: vc.index.astype(str),
|
|
36
|
+
"Count": vc.values
|
|
37
|
+
})
|
|
38
|
+
title = f"{session.file} (unique counts: {col})"
|
|
39
|
+
else:
|
|
40
|
+
uniques = df[col].drop_duplicates()
|
|
41
|
+
result = pd.DataFrame({col: uniques.astype(str).values})
|
|
42
|
+
title = f"{session.file} (unique: {col})"
|
|
43
|
+
|
|
44
|
+
render(result, title)
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
from pfc.core.state import session
|
|
2
|
+
from pfc.ui.table import render
|
|
3
|
+
from rich.console import Console
|
|
4
|
+
import pandas as pd
|
|
5
|
+
|
|
6
|
+
console = Console()
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def run(args):
|
|
10
|
+
if session.df is None:
|
|
11
|
+
console.print("[red]No dataset loaded[/red]")
|
|
12
|
+
return
|
|
13
|
+
|
|
14
|
+
if not args:
|
|
15
|
+
console.print("[red]Usage:[/red] value_counts <column> [--normalize]")
|
|
16
|
+
return
|
|
17
|
+
|
|
18
|
+
df = session.df.df
|
|
19
|
+
normalize = False
|
|
20
|
+
|
|
21
|
+
if "--normalize" in args:
|
|
22
|
+
normalize = True
|
|
23
|
+
args.remove("--normalize")
|
|
24
|
+
|
|
25
|
+
col = " ".join(args)
|
|
26
|
+
|
|
27
|
+
if col not in df.columns:
|
|
28
|
+
console.print(f"[red]Invalid column:[/red] {col}")
|
|
29
|
+
return
|
|
30
|
+
|
|
31
|
+
counts = df[col].value_counts(dropna=False)
|
|
32
|
+
|
|
33
|
+
result = pd.DataFrame({
|
|
34
|
+
col: counts.index.astype(str),
|
|
35
|
+
"Count": counts.values
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
if normalize:
|
|
39
|
+
total = result["Count"].sum()
|
|
40
|
+
result["Percentage"] = (result["Count"] / total * 100).round(2)
|
|
41
|
+
|
|
42
|
+
render(result, f"{session.file} (value_counts: {col})")
|
pfc/core/__init__.py
ADDED
|
File without changes
|
pfc/core/dataset.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# pfc/core/dataset.py
|
|
2
|
+
import pandas as pd
|
|
3
|
+
|
|
4
|
+
class Dataset:
|
|
5
|
+
def __init__(self, path):
|
|
6
|
+
self.path = path
|
|
7
|
+
self.df = pd.read_csv(path)
|
|
8
|
+
|
|
9
|
+
def head(self, n=5):
|
|
10
|
+
return self.df.head(n).copy()
|
|
11
|
+
|
|
12
|
+
def tail(self, n=5):
|
|
13
|
+
return self.df.tail(n)
|
|
14
|
+
|
|
15
|
+
def columns(self):
|
|
16
|
+
return list(self.df.columns)
|
|
17
|
+
|
|
18
|
+
def describe(self):
|
|
19
|
+
return self.df.describe
|
|
20
|
+
|
|
21
|
+
def size(self):
|
|
22
|
+
return self.df.size
|
|
23
|
+
|
|
24
|
+
def index(self):
|
|
25
|
+
return self.df.index
|
pfc/core/state.py
ADDED
pfc/main.py
ADDED
pfc/shell/__init__.py
ADDED
|
File without changes
|
pfc/shell/parser.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# pfc/shell/parser.py
|
|
2
|
+
from pfc.commands import head, tail, cols, load, help, describe, size, index, info, select, value_counts, groupby, unique, barplot, histplot, lineplot, cls, heatmap
|
|
3
|
+
|
|
4
|
+
COMMANDS = {
|
|
5
|
+
"help": help.run,
|
|
6
|
+
"cls": cls.run,
|
|
7
|
+
|
|
8
|
+
"load": load.run,
|
|
9
|
+
|
|
10
|
+
"head": head.run,
|
|
11
|
+
"tail": tail.run,
|
|
12
|
+
"cols": cols.run,
|
|
13
|
+
|
|
14
|
+
"describe": describe.run,
|
|
15
|
+
"size": size.run,
|
|
16
|
+
|
|
17
|
+
"index": index.run,
|
|
18
|
+
|
|
19
|
+
"info": info.run,
|
|
20
|
+
|
|
21
|
+
"select": select.run,
|
|
22
|
+
|
|
23
|
+
"value_counts": value_counts.run,
|
|
24
|
+
|
|
25
|
+
"groupby": groupby.run,
|
|
26
|
+
|
|
27
|
+
"unique": unique.run,
|
|
28
|
+
|
|
29
|
+
#--------------------------------Visualization Commands-----------------------------------------
|
|
30
|
+
|
|
31
|
+
"barplot": barplot.run,
|
|
32
|
+
"histplot": histplot.run,
|
|
33
|
+
"lineplot": lineplot.run,
|
|
34
|
+
"heatmap": heatmap.run
|
|
35
|
+
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
def dispatch(cmd, args):
|
|
39
|
+
if cmd in COMMANDS:
|
|
40
|
+
COMMANDS[cmd](args)
|
|
41
|
+
else:
|
|
42
|
+
print("❌ Unknown command")
|
pfc/shell/repl.py
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# pfc/shell/repl.py
|
|
2
|
+
from pfc.shell.parser import dispatch
|
|
3
|
+
|
|
4
|
+
def start():
|
|
5
|
+
print("pfc — Interactive Data Shell")
|
|
6
|
+
print("Load a dataset once and explore it using built-in commands.")
|
|
7
|
+
print("Type 'help' to see available commands. Type 'exit' to quit.\n")
|
|
8
|
+
|
|
9
|
+
while True:
|
|
10
|
+
raw = input("pfc> ").strip()
|
|
11
|
+
if not raw:
|
|
12
|
+
continue
|
|
13
|
+
|
|
14
|
+
if raw in {"exit", "quit"}:
|
|
15
|
+
break
|
|
16
|
+
|
|
17
|
+
parts = raw.split()
|
|
18
|
+
dispatch(parts[0], parts[1:])
|
pfc/ui/__init__.py
ADDED
|
File without changes
|
pfc/ui/styles.py
ADDED
|
File without changes
|
pfc/ui/table.py
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# # pfc/ui/table.py
|
|
2
|
+
# from rich.table import Table
|
|
3
|
+
# from rich.console import Console
|
|
4
|
+
|
|
5
|
+
# console = Console()
|
|
6
|
+
|
|
7
|
+
# def render(df, title):
|
|
8
|
+
# table = Table(title=title, header_style="bold magenta", row_styles=["none", "dim"])
|
|
9
|
+
# for col in df.columns:
|
|
10
|
+
# table.add_column(col, max_width=20, overflow="ellipsis")
|
|
11
|
+
|
|
12
|
+
# for _, row in df.iterrows():
|
|
13
|
+
# table.add_row(*[str(v) for v in row])
|
|
14
|
+
|
|
15
|
+
# console.print(table)
|
|
16
|
+
|
|
17
|
+
# from rich.console import Console
|
|
18
|
+
# from rich.table import Table
|
|
19
|
+
|
|
20
|
+
# console = Console()
|
|
21
|
+
|
|
22
|
+
# MAX_VISIBLE_ROWS = 10
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# def render(df, title):
|
|
26
|
+
# table = Table(
|
|
27
|
+
# title=title,
|
|
28
|
+
# header_style="bold magenta",
|
|
29
|
+
# row_styles=["none", "dim"]
|
|
30
|
+
# )
|
|
31
|
+
|
|
32
|
+
# for col in df.columns:
|
|
33
|
+
# table.add_column(col, max_width=20, overflow="ellipsis")
|
|
34
|
+
|
|
35
|
+
# for _, row in df.iterrows():
|
|
36
|
+
# table.add_row(*[str(v) for v in row])
|
|
37
|
+
|
|
38
|
+
# # 🔥 Scroll if too many rows
|
|
39
|
+
# if len(df) > MAX_VISIBLE_ROWS:
|
|
40
|
+
# with console.pager():
|
|
41
|
+
# console.print(table)
|
|
42
|
+
# else:
|
|
43
|
+
# console.print(table)
|
|
44
|
+
|
|
45
|
+
from rich.console import Console
|
|
46
|
+
from rich.table import Table
|
|
47
|
+
import os
|
|
48
|
+
|
|
49
|
+
console = Console()
|
|
50
|
+
MAX_VISIBLE_ROWS = 10
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def render(df, title):
|
|
54
|
+
rows = len(df)
|
|
55
|
+
|
|
56
|
+
# 🔵 Case 1: Small table → normal render
|
|
57
|
+
if rows <= MAX_VISIBLE_ROWS:
|
|
58
|
+
_print_table(df, title)
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
# 🔵 Case 2: Unix → pager
|
|
62
|
+
if os.name != "nt":
|
|
63
|
+
with console.pager():
|
|
64
|
+
_print_table(df, title)
|
|
65
|
+
return
|
|
66
|
+
|
|
67
|
+
# 🔵 Case 3: Windows → manual paging
|
|
68
|
+
_render_windows_paged(df, title)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _print_table(df, title):
|
|
72
|
+
table = Table(
|
|
73
|
+
title=title,
|
|
74
|
+
header_style="bold magenta",
|
|
75
|
+
row_styles=["none", "dim"],
|
|
76
|
+
show_lines=False
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
for col in df.columns:
|
|
80
|
+
table.add_column(col, max_width=20, overflow="ellipsis")
|
|
81
|
+
|
|
82
|
+
for _, row in df.iterrows():
|
|
83
|
+
table.add_row(*[str(v) for v in row])
|
|
84
|
+
|
|
85
|
+
console.print(table)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _render_windows_paged(df, title):
|
|
89
|
+
total = len(df)
|
|
90
|
+
start = 0
|
|
91
|
+
|
|
92
|
+
while start < total:
|
|
93
|
+
end = min(start + MAX_VISIBLE_ROWS, total)
|
|
94
|
+
chunk = df.iloc[start:end]
|
|
95
|
+
|
|
96
|
+
page_title = f"{title} (rows {start + 1}-{end})"
|
|
97
|
+
_print_table(chunk, page_title)
|
|
98
|
+
|
|
99
|
+
start = end
|
|
100
|
+
if start >= total:
|
|
101
|
+
break
|
|
102
|
+
|
|
103
|
+
console.print("[dim]Press Enter for next page, q to quit[/dim]", end="")
|
|
104
|
+
choice = input().strip().lower()
|
|
105
|
+
if choice == "q":
|
|
106
|
+
break
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pfc-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Terminal-first data exploration and visualization tool for CSV files
|
|
5
|
+
Author-email: Shivam <shivamsharma22468@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: cli,data-analysis,eda,csv,terminal
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Requires-Python: >=3.8
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: pandas
|
|
15
|
+
Requires-Dist: numpy
|
|
16
|
+
Requires-Dist: rich
|
|
17
|
+
Dynamic: license-file
|
|
18
|
+
|
|
19
|
+
# pfc (Pandas For CLI)
|
|
20
|
+
|
|
21
|
+
pfc is a terminal-first data exploration and visualization tool for CSV files.
|
|
22
|
+
It lets you analyze datasets interactively without leaving the terminal.
|
|
23
|
+
|
|
24
|
+
## Features
|
|
25
|
+
|
|
26
|
+
- Load CSV once and explore interactively
|
|
27
|
+
- Head / tail / select / describe
|
|
28
|
+
- Groupby, value_counts, unique
|
|
29
|
+
- Terminal visualizations:
|
|
30
|
+
- barplot
|
|
31
|
+
- histogram
|
|
32
|
+
- lineplot
|
|
33
|
+
- heatmap
|
|
34
|
+
- Works on Windows, Linux, macOS
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pip install pfc-cli
|
|
40
|
+
```
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
pfc/main.py,sha256=17lR2RLbFIB07HZQvzh1jEgg8NmOBrshAD_402_LgBA,205
|
|
2
|
+
pfc/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
pfc/commands/barplot.py,sha256=ADZbQzIqx6IShl3vycnRQInpPRmAQMCgZHHNJyjWBqA,1511
|
|
4
|
+
pfc/commands/cls.py,sha256=anPj9Q16TSJeoS3ju2tRN1g4QqHSTL9AyIpzSNWB7yo,456
|
|
5
|
+
pfc/commands/cols.py,sha256=d_LpWynJ5WahiWWEiGF0kuyHs5OefFKt7RcPCv2DSu8,486
|
|
6
|
+
pfc/commands/describe.py,sha256=_gDErxD21k6vyxNA4DdeKu4cwup2BBHSs6Y6xD4ZTtA,3591
|
|
7
|
+
pfc/commands/groupby.py,sha256=vIm1EkscbRL4nkNRIwXXi5D-et_BfDSRQK3PWT7O94k,1413
|
|
8
|
+
pfc/commands/head.py,sha256=RQpHgUsMiqFT0nyPWZcqisFnBOQOueDuv965EJRI1rM,285
|
|
9
|
+
pfc/commands/heatmap.py,sha256=sF_7gkiykbYmW2goOxRYVVhS70_niHwXXaZK2R1HyZI,2641
|
|
10
|
+
pfc/commands/help.py,sha256=ZgFV1uDyjqVMfZ7tHWTlZHU8X672wfNidP3bGaASreY,3563
|
|
11
|
+
pfc/commands/histplot.py,sha256=DOtGAaoViW9u3gjedX32vDLHVUK6Ekc9Ju8me5UgbLI,2129
|
|
12
|
+
pfc/commands/index.py,sha256=ELZCCPI9782ekCIgFf6V0mT5jm98UOabqHVAEeQp2Gw,1188
|
|
13
|
+
pfc/commands/info.py,sha256=MZb0rQ5880DTHgbe0DUArR_ul1CzhHZO0inxVG3tVz8,1582
|
|
14
|
+
pfc/commands/lineplot.py,sha256=6J_UBoC1R0xltACkTm7DcMknVm_F_-OBDx6k_qs1VW4,2849
|
|
15
|
+
pfc/commands/load.py,sha256=w-KfpU2WrQ7G7NoFq27ymUiC_UvOf4DGBdAhv16sZjk,511
|
|
16
|
+
pfc/commands/select.py,sha256=ztosF3MH-YYepuM20AymaIblnWx0sz_xJbzHLOUQ-eY,985
|
|
17
|
+
pfc/commands/size.py,sha256=sCqbdy5Oh4BZZQcTOB-ShGzNcwfs_9pzJ1Sef9Jf7EM,624
|
|
18
|
+
pfc/commands/tail.py,sha256=viI-tbtqvXH5-sm5PpFRCpvLlQEXt7SC2GZmgPmGFPE,285
|
|
19
|
+
pfc/commands/unique.py,sha256=28R4vex_ZRbdubL4p2C9OgbHqAC7jChmdq4m4hg5-FM,1132
|
|
20
|
+
pfc/commands/value_counts.py,sha256=V1xu-PCNGQUVwbbvCMUqdKV49qk7TlblAjNZ-hK6zPw,979
|
|
21
|
+
pfc/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
+
pfc/core/dataset.py,sha256=KgIGUtObWiB7S9mK9sEOUHSxxDdK8doI9CftPFANiwc,488
|
|
23
|
+
pfc/core/state.py,sha256=tVMcrWp7rJg3hNXiYY_Z2hFwwz-nXzOlwVuPt-sJcgU,128
|
|
24
|
+
pfc/shell/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
25
|
+
pfc/shell/parser.py,sha256=VUzE7JCJGvarDXSxEN8nwcbEmm5bDaEvtkgJtttSX10,902
|
|
26
|
+
pfc/shell/repl.py,sha256=0T7_biAZ7uG23cgr_iQoaSaRBKHdwm2x1Ghn4tW7RK4,481
|
|
27
|
+
pfc/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
|
+
pfc/ui/styles.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
29
|
+
pfc/ui/table.py,sha256=aXbT8-qb2LjFs57MxQtUg4UCOlHOixtlgChIyOcn7YY,2474
|
|
30
|
+
pfc_cli-0.1.0.dist-info/licenses/LICENSE,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
31
|
+
pfc_cli-0.1.0.dist-info/METADATA,sha256=hq7wWAmyxR3IGORmzq7mgTXtw-9qu1JKhfIZk2A87cU,1054
|
|
32
|
+
pfc_cli-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
33
|
+
pfc_cli-0.1.0.dist-info/entry_points.txt,sha256=Yz-2mCzaBKlmJfKHqu_LBuM4fuD9LB4rklIPK9oAGvU,39
|
|
34
|
+
pfc_cli-0.1.0.dist-info/top_level.txt,sha256=n087vINEh-4CtgHhJqpOn2aUqJUxdnPoj-1LpiE7L3Y,4
|
|
35
|
+
pfc_cli-0.1.0.dist-info/RECORD,,
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pfc
|