tablofy 1.0.0a0__tar.gz
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.
- tablofy-1.0.0a0/LICENSE +21 -0
- tablofy-1.0.0a0/PKG-INFO +242 -0
- tablofy-1.0.0a0/README.md +216 -0
- tablofy-1.0.0a0/pyproject.toml +51 -0
- tablofy-1.0.0a0/setup.cfg +4 -0
- tablofy-1.0.0a0/src/tablofy/__init__.py +28 -0
- tablofy-1.0.0a0/src/tablofy/analytics/__init__.py +1 -0
- tablofy-1.0.0a0/src/tablofy/analytics/insights.py +130 -0
- tablofy-1.0.0a0/src/tablofy/analytics/outliers.py +81 -0
- tablofy-1.0.0a0/src/tablofy/analytics/stats.py +115 -0
- tablofy-1.0.0a0/src/tablofy/cleaning/__init__.py +1 -0
- tablofy-1.0.0a0/src/tablofy/cleaning/cleaner.py +132 -0
- tablofy-1.0.0a0/src/tablofy/cleaning/report.py +29 -0
- tablofy-1.0.0a0/src/tablofy/core/__init__.py +1 -0
- tablofy-1.0.0a0/src/tablofy/core/config.py +11 -0
- tablofy-1.0.0a0/src/tablofy/core/errors.py +25 -0
- tablofy-1.0.0a0/src/tablofy/core/frame.py +641 -0
- tablofy-1.0.0a0/src/tablofy/core/loader.py +62 -0
- tablofy-1.0.0a0/src/tablofy/exploration/__init__.py +1 -0
- tablofy-1.0.0a0/src/tablofy/exploration/profile.py +59 -0
- tablofy-1.0.0a0/src/tablofy/exploration/schema.py +53 -0
- tablofy-1.0.0a0/src/tablofy/exploration/summary.py +32 -0
- tablofy-1.0.0a0/src/tablofy/reports/__init__.py +1 -0
- tablofy-1.0.0a0/src/tablofy/reports/excel.py +65 -0
- tablofy-1.0.0a0/src/tablofy/reports/html.py +71 -0
- tablofy-1.0.0a0/src/tablofy/sql/__init__.py +1 -0
- tablofy-1.0.0a0/src/tablofy/sql/duckdb_query.py +77 -0
- tablofy-1.0.0a0/src/tablofy/transform/__init__.py +1 -0
- tablofy-1.0.0a0/src/tablofy/transform/group.py +71 -0
- tablofy-1.0.0a0/src/tablofy/transform/operations.py +213 -0
- tablofy-1.0.0a0/src/tablofy/transform/safe_filter.py +76 -0
- tablofy-1.0.0a0/src/tablofy/utils/__init__.py +1 -0
- tablofy-1.0.0a0/src/tablofy/utils/formatting.py +50 -0
- tablofy-1.0.0a0/src/tablofy/utils/lazy_imports.py +23 -0
- tablofy-1.0.0a0/src/tablofy/utils/validation.py +70 -0
- tablofy-1.0.0a0/src/tablofy/visualization/__init__.py +1 -0
- tablofy-1.0.0a0/src/tablofy/visualization/charts.py +130 -0
- tablofy-1.0.0a0/src/tablofy/visualization/smart_chart.py +72 -0
- tablofy-1.0.0a0/src/tablofy.egg-info/PKG-INFO +242 -0
- tablofy-1.0.0a0/src/tablofy.egg-info/SOURCES.txt +50 -0
- tablofy-1.0.0a0/src/tablofy.egg-info/dependency_links.txt +1 -0
- tablofy-1.0.0a0/src/tablofy.egg-info/requires.txt +16 -0
- tablofy-1.0.0a0/src/tablofy.egg-info/top_level.txt +1 -0
- tablofy-1.0.0a0/tests/test_analytics.py +199 -0
- tablofy-1.0.0a0/tests/test_cleaning.py +187 -0
- tablofy-1.0.0a0/tests/test_exploration.py +139 -0
- tablofy-1.0.0a0/tests/test_loader.py +111 -0
- tablofy-1.0.0a0/tests/test_reports.py +33 -0
- tablofy-1.0.0a0/tests/test_sql.py +73 -0
- tablofy-1.0.0a0/tests/test_table.py +62 -0
- tablofy-1.0.0a0/tests/test_transform.py +309 -0
- tablofy-1.0.0a0/tests/test_visualization.py +171 -0
tablofy-1.0.0a0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tablofy Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
tablofy-1.0.0a0/PKG-INFO
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tablofy
|
|
3
|
+
Version: 1.0.0a0
|
|
4
|
+
Summary: A beginner-friendly data analytics library wrapping pandas, matplotlib, seaborn, and DuckDB.
|
|
5
|
+
Author: Tablofy Contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.9
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Requires-Dist: pandas>=2.0
|
|
11
|
+
Requires-Dist: numpy>=1.24
|
|
12
|
+
Requires-Dist: matplotlib>=3.7
|
|
13
|
+
Requires-Dist: seaborn>=0.12
|
|
14
|
+
Requires-Dist: plotly>=5.14
|
|
15
|
+
Requires-Dist: duckdb>=0.8
|
|
16
|
+
Requires-Dist: pyarrow>=12
|
|
17
|
+
Requires-Dist: openpyxl>=3.1
|
|
18
|
+
Requires-Dist: jinja2>=3.1
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
21
|
+
Requires-Dist: ruff>=0.1; extra == "dev"
|
|
22
|
+
Requires-Dist: black>=23; extra == "dev"
|
|
23
|
+
Requires-Dist: build>=1.0; extra == "dev"
|
|
24
|
+
Requires-Dist: twine>=4.0; extra == "dev"
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# Tablofy
|
|
28
|
+
|
|
29
|
+
A beginner-friendly data analytics library for Python that wraps pandas, matplotlib, seaborn, and DuckDB under a simple, unified API.
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
import tablofy as tf
|
|
33
|
+
|
|
34
|
+
data = tf.load("sales.csv")
|
|
35
|
+
data.clean()
|
|
36
|
+
print(data.profile())
|
|
37
|
+
data.chart("sales by month")
|
|
38
|
+
print(data.insights())
|
|
39
|
+
data.report("report.html")
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## What problem does Tablofy solve?
|
|
43
|
+
|
|
44
|
+
Pandas is powerful but has a steep learning curve. Tablofy wraps it so you can:
|
|
45
|
+
|
|
46
|
+
- **Load** CSV, Excel, JSON, and Parquet with a single function
|
|
47
|
+
- **Explore** your data with plain-English methods (`preview()`, `profile()`, `summary()`)
|
|
48
|
+
- **Clean** everything in one call (`clean()`) — duplicates, missing values, column names, dates, whitespace
|
|
49
|
+
- **Chart** without memorizing matplotlib or seaborn APIs
|
|
50
|
+
- **Run SQL** directly on a DataFrame using DuckDB
|
|
51
|
+
- **Report** with a single call to `report("report.html")`
|
|
52
|
+
|
|
53
|
+
No complex pandas syntax. No plotting boilerplate. No separate SQL database setup.
|
|
54
|
+
|
|
55
|
+
## Installation
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install tablofy
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Requires Python 3.9 or later.
|
|
62
|
+
|
|
63
|
+
## Quick Start
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
import tablofy as tf
|
|
67
|
+
|
|
68
|
+
# Load a file
|
|
69
|
+
data = tf.load("sales.csv")
|
|
70
|
+
|
|
71
|
+
# Explore
|
|
72
|
+
print(data.preview()) # first 5 rows
|
|
73
|
+
print(data.shape()) # {"rows": 100, "columns": 8}
|
|
74
|
+
print(data.columns()) # ["date", "region", "product", "sales"]
|
|
75
|
+
print(data.profile()) # full dataset profile
|
|
76
|
+
|
|
77
|
+
# Clean in one go
|
|
78
|
+
data.clean()
|
|
79
|
+
|
|
80
|
+
# Chart
|
|
81
|
+
data.bar(x="region", y="sales")
|
|
82
|
+
data.chart("sales by month") # plain English!
|
|
83
|
+
|
|
84
|
+
# Get automated insights
|
|
85
|
+
for note in data.insights():
|
|
86
|
+
print(f" - {note}")
|
|
87
|
+
|
|
88
|
+
# Run SQL
|
|
89
|
+
print(data.sql("SELECT region, SUM(sales) FROM data GROUP BY region"))
|
|
90
|
+
|
|
91
|
+
# Generate a report
|
|
92
|
+
data.report("report.html")
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Supported file formats
|
|
96
|
+
|
|
97
|
+
| Extension | Format | Load | Export |
|
|
98
|
+
|-----------|--------|------|--------|
|
|
99
|
+
| `.csv` | Comma-separated values | `tf.load()` | `data.export()` |
|
|
100
|
+
| `.xlsx` | Excel workbook | `tf.load()` | `data.export()` |
|
|
101
|
+
| `.xls` | Excel 97-2003 | `tf.load()` | — |
|
|
102
|
+
| `.json` | JSON | `tf.load()` | `data.export()` |
|
|
103
|
+
| `.parquet` | Apache Parquet | `tf.load()` | `data.export()` |
|
|
104
|
+
|
|
105
|
+
## API overview
|
|
106
|
+
|
|
107
|
+
### Loading
|
|
108
|
+
|
|
109
|
+
| Method | Description |
|
|
110
|
+
|--------|-------------|
|
|
111
|
+
| `tf.load(path)` | Load a file (auto-detects format) |
|
|
112
|
+
| `TablofyFrame(df, name)` | Wrap an existing pandas DataFrame |
|
|
113
|
+
|
|
114
|
+
### Exploration
|
|
115
|
+
|
|
116
|
+
| Method | Description |
|
|
117
|
+
|--------|-------------|
|
|
118
|
+
| `.preview(n=5)` | First *n* rows |
|
|
119
|
+
| `.head(n=5)` | Alias for preview |
|
|
120
|
+
| `.shape()` | Row and column counts (dict) |
|
|
121
|
+
| `.columns()` | Column names (list) |
|
|
122
|
+
| `.dtypes` | Column -> dtype mapping (dict) |
|
|
123
|
+
| `.types()` | Column types and null counts (DataFrame) |
|
|
124
|
+
| `.missing()` | Columns with null values (DataFrame) |
|
|
125
|
+
| `.duplicates()` | Duplicate row stats (dict) |
|
|
126
|
+
| `.summary()` | Descriptive statistics (DataFrame) |
|
|
127
|
+
| `.profile()` | Full dataset profile (dict) |
|
|
128
|
+
| `.size` | Total cell count (int) |
|
|
129
|
+
| `len(data)` | Row count (int) |
|
|
130
|
+
| `.to_pandas()` | Get the underlying pandas DataFrame |
|
|
131
|
+
|
|
132
|
+
### Cleaning
|
|
133
|
+
|
|
134
|
+
| Method | Description |
|
|
135
|
+
|--------|-------------|
|
|
136
|
+
| `.clean()` | Clean in-place: duplicates, missing, column names, dates, whitespace |
|
|
137
|
+
| `.clean_report()` | Report of actions from last clean (dict) |
|
|
138
|
+
|
|
139
|
+
### Transform
|
|
140
|
+
|
|
141
|
+
| Method | Description |
|
|
142
|
+
|--------|-------------|
|
|
143
|
+
| `.select(*cols)` | New frame with only the given columns |
|
|
144
|
+
| `.drop(col)` | New frame without a column |
|
|
145
|
+
| `.rename(mapping)` | New frame with renamed columns |
|
|
146
|
+
| `.sort(by, descending)` | New frame sorted by a column |
|
|
147
|
+
| `.filter(expression)` | New frame filtered by a pandas query expression |
|
|
148
|
+
| `.group(by)` | A `GroupedFrame` for `.sum()` / `.mean()` aggregation |
|
|
149
|
+
| `.pivot(index, columns, values)` | Pivot table |
|
|
150
|
+
| `.join(other, on, how)` | Merge two frames on a shared key |
|
|
151
|
+
| `.export(path)` | Write to CSV, XLSX, JSON, or Parquet |
|
|
152
|
+
|
|
153
|
+
### Visualization
|
|
154
|
+
|
|
155
|
+
| Method | Description |
|
|
156
|
+
|--------|-------------|
|
|
157
|
+
| `.bar(x, y, save)` | Bar chart |
|
|
158
|
+
| `.line(x, y, save)` | Line chart |
|
|
159
|
+
| `.scatter(x, y, save)` | Scatter plot |
|
|
160
|
+
| `.hist(col, save)` | Histogram |
|
|
161
|
+
| `.box(x, y, save)` | Box plot |
|
|
162
|
+
| `.heatmap(save)` | Correlation heatmap |
|
|
163
|
+
| `.pairplot(save)` | Pairwise scatter matrix |
|
|
164
|
+
| `.chart(description)` | Smart chart from plain English |
|
|
165
|
+
|
|
166
|
+
### Analytics
|
|
167
|
+
|
|
168
|
+
| Method | Description |
|
|
169
|
+
|--------|-------------|
|
|
170
|
+
| `.insights()` | Rule-based observations (list of strings) |
|
|
171
|
+
| `.stats.describe()` | Descriptive statistics (DataFrame) |
|
|
172
|
+
| `.stats.correlation()` | Correlation matrix (DataFrame) |
|
|
173
|
+
| `.stats.covariance()` | Covariance matrix (DataFrame) |
|
|
174
|
+
| `.stats.outliers(col)` | IQR outlier detection |
|
|
175
|
+
| `.stats.mean(col)` | Mean of a column |
|
|
176
|
+
| `.stats.median(col)` | Median of a column |
|
|
177
|
+
| `.stats.std(col)` | Standard deviation |
|
|
178
|
+
| `.stats.min(col)` | Minimum value |
|
|
179
|
+
| `.stats.max(col)` | Maximum value |
|
|
180
|
+
| `.stats.quantile(col, q)` | Quantile value |
|
|
181
|
+
| `.stats.value_counts(col)` | Frequency counts (Series) |
|
|
182
|
+
|
|
183
|
+
### SQL
|
|
184
|
+
|
|
185
|
+
| Method | Description |
|
|
186
|
+
|--------|-------------|
|
|
187
|
+
| `.sql(query)` | Run read-only SQL via DuckDB (table name: `data`) |
|
|
188
|
+
|
|
189
|
+
### Reports
|
|
190
|
+
|
|
191
|
+
| Method | Description |
|
|
192
|
+
|--------|-------------|
|
|
193
|
+
| `.report(path)` | Generate HTML or Excel report |
|
|
194
|
+
|
|
195
|
+
## Examples
|
|
196
|
+
|
|
197
|
+
See the [`examples/`](examples/) directory for runnable scripts:
|
|
198
|
+
|
|
199
|
+
| Script | What it shows |
|
|
200
|
+
|--------|---------------|
|
|
201
|
+
| `basic_usage.py` | Core workflow: load → inspect → clean → chart |
|
|
202
|
+
| `exploration_demo.py` | All exploration methods |
|
|
203
|
+
| `cleaning_demo.py` | Cleaning pipeline + report |
|
|
204
|
+
| `transform_demo.py` | Select, filter, group, sort, pivot, chain |
|
|
205
|
+
| `charts_demo.py` | All chart types + smart chart |
|
|
206
|
+
| `analytics_demo.py` | Insights + statistics |
|
|
207
|
+
| `sql_demo.py` | SQL queries + chaining |
|
|
208
|
+
| `report_demo.py` | HTML + Excel reports |
|
|
209
|
+
|
|
210
|
+
## What Tablofy is not
|
|
211
|
+
|
|
212
|
+
- **Not a replacement for pandas** — you can always get the underlying DataFrame with `.to_pandas()`
|
|
213
|
+
- **Not a big-data tool** — it operates in-memory on a single machine
|
|
214
|
+
- **Not a machine-learning library** — no model training or prediction
|
|
215
|
+
- **Not a dashboard tool** — reports are static HTML or Excel files
|
|
216
|
+
- **Not a database** — SQL runs in-memory against the loaded DataFrame only
|
|
217
|
+
|
|
218
|
+
## Roadmap
|
|
219
|
+
|
|
220
|
+
- [x] Load CSV, Excel, JSON, Parquet
|
|
221
|
+
- [x] Data exploration (profile, summary, missing, types)
|
|
222
|
+
- [x] Data cleaning (duplicates, missing values, column names, dates)
|
|
223
|
+
- [x] Transformations (select, drop, sort, filter, group, join)
|
|
224
|
+
- [x] Visualization (bar, line, scatter, hist, box, heatmap, pairplot)
|
|
225
|
+
- [x] Smart chart from plain-English descriptions
|
|
226
|
+
- [x] Rule-based insights
|
|
227
|
+
- [x] Statistical helpers (mean, median, outliers, correlation)
|
|
228
|
+
- [x] SQL queries via DuckDB
|
|
229
|
+
- [x] HTML and Excel reports
|
|
230
|
+
- [ ] Interactive charts with Plotly
|
|
231
|
+
- [ ] Time-series analysis helpers
|
|
232
|
+
- [ ] More chart types (area, pie, violin)
|
|
233
|
+
- [ ] Custom theme/style configuration
|
|
234
|
+
- [ ] Integration with Jupyter Notebook widgets
|
|
235
|
+
|
|
236
|
+
## Contributing
|
|
237
|
+
|
|
238
|
+
Contributions are welcome! Open an issue or pull request on GitHub.
|
|
239
|
+
|
|
240
|
+
## License
|
|
241
|
+
|
|
242
|
+
MIT
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
# Tablofy
|
|
2
|
+
|
|
3
|
+
A beginner-friendly data analytics library for Python that wraps pandas, matplotlib, seaborn, and DuckDB under a simple, unified API.
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
import tablofy as tf
|
|
7
|
+
|
|
8
|
+
data = tf.load("sales.csv")
|
|
9
|
+
data.clean()
|
|
10
|
+
print(data.profile())
|
|
11
|
+
data.chart("sales by month")
|
|
12
|
+
print(data.insights())
|
|
13
|
+
data.report("report.html")
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## What problem does Tablofy solve?
|
|
17
|
+
|
|
18
|
+
Pandas is powerful but has a steep learning curve. Tablofy wraps it so you can:
|
|
19
|
+
|
|
20
|
+
- **Load** CSV, Excel, JSON, and Parquet with a single function
|
|
21
|
+
- **Explore** your data with plain-English methods (`preview()`, `profile()`, `summary()`)
|
|
22
|
+
- **Clean** everything in one call (`clean()`) — duplicates, missing values, column names, dates, whitespace
|
|
23
|
+
- **Chart** without memorizing matplotlib or seaborn APIs
|
|
24
|
+
- **Run SQL** directly on a DataFrame using DuckDB
|
|
25
|
+
- **Report** with a single call to `report("report.html")`
|
|
26
|
+
|
|
27
|
+
No complex pandas syntax. No plotting boilerplate. No separate SQL database setup.
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install tablofy
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Requires Python 3.9 or later.
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
import tablofy as tf
|
|
41
|
+
|
|
42
|
+
# Load a file
|
|
43
|
+
data = tf.load("sales.csv")
|
|
44
|
+
|
|
45
|
+
# Explore
|
|
46
|
+
print(data.preview()) # first 5 rows
|
|
47
|
+
print(data.shape()) # {"rows": 100, "columns": 8}
|
|
48
|
+
print(data.columns()) # ["date", "region", "product", "sales"]
|
|
49
|
+
print(data.profile()) # full dataset profile
|
|
50
|
+
|
|
51
|
+
# Clean in one go
|
|
52
|
+
data.clean()
|
|
53
|
+
|
|
54
|
+
# Chart
|
|
55
|
+
data.bar(x="region", y="sales")
|
|
56
|
+
data.chart("sales by month") # plain English!
|
|
57
|
+
|
|
58
|
+
# Get automated insights
|
|
59
|
+
for note in data.insights():
|
|
60
|
+
print(f" - {note}")
|
|
61
|
+
|
|
62
|
+
# Run SQL
|
|
63
|
+
print(data.sql("SELECT region, SUM(sales) FROM data GROUP BY region"))
|
|
64
|
+
|
|
65
|
+
# Generate a report
|
|
66
|
+
data.report("report.html")
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Supported file formats
|
|
70
|
+
|
|
71
|
+
| Extension | Format | Load | Export |
|
|
72
|
+
|-----------|--------|------|--------|
|
|
73
|
+
| `.csv` | Comma-separated values | `tf.load()` | `data.export()` |
|
|
74
|
+
| `.xlsx` | Excel workbook | `tf.load()` | `data.export()` |
|
|
75
|
+
| `.xls` | Excel 97-2003 | `tf.load()` | — |
|
|
76
|
+
| `.json` | JSON | `tf.load()` | `data.export()` |
|
|
77
|
+
| `.parquet` | Apache Parquet | `tf.load()` | `data.export()` |
|
|
78
|
+
|
|
79
|
+
## API overview
|
|
80
|
+
|
|
81
|
+
### Loading
|
|
82
|
+
|
|
83
|
+
| Method | Description |
|
|
84
|
+
|--------|-------------|
|
|
85
|
+
| `tf.load(path)` | Load a file (auto-detects format) |
|
|
86
|
+
| `TablofyFrame(df, name)` | Wrap an existing pandas DataFrame |
|
|
87
|
+
|
|
88
|
+
### Exploration
|
|
89
|
+
|
|
90
|
+
| Method | Description |
|
|
91
|
+
|--------|-------------|
|
|
92
|
+
| `.preview(n=5)` | First *n* rows |
|
|
93
|
+
| `.head(n=5)` | Alias for preview |
|
|
94
|
+
| `.shape()` | Row and column counts (dict) |
|
|
95
|
+
| `.columns()` | Column names (list) |
|
|
96
|
+
| `.dtypes` | Column -> dtype mapping (dict) |
|
|
97
|
+
| `.types()` | Column types and null counts (DataFrame) |
|
|
98
|
+
| `.missing()` | Columns with null values (DataFrame) |
|
|
99
|
+
| `.duplicates()` | Duplicate row stats (dict) |
|
|
100
|
+
| `.summary()` | Descriptive statistics (DataFrame) |
|
|
101
|
+
| `.profile()` | Full dataset profile (dict) |
|
|
102
|
+
| `.size` | Total cell count (int) |
|
|
103
|
+
| `len(data)` | Row count (int) |
|
|
104
|
+
| `.to_pandas()` | Get the underlying pandas DataFrame |
|
|
105
|
+
|
|
106
|
+
### Cleaning
|
|
107
|
+
|
|
108
|
+
| Method | Description |
|
|
109
|
+
|--------|-------------|
|
|
110
|
+
| `.clean()` | Clean in-place: duplicates, missing, column names, dates, whitespace |
|
|
111
|
+
| `.clean_report()` | Report of actions from last clean (dict) |
|
|
112
|
+
|
|
113
|
+
### Transform
|
|
114
|
+
|
|
115
|
+
| Method | Description |
|
|
116
|
+
|--------|-------------|
|
|
117
|
+
| `.select(*cols)` | New frame with only the given columns |
|
|
118
|
+
| `.drop(col)` | New frame without a column |
|
|
119
|
+
| `.rename(mapping)` | New frame with renamed columns |
|
|
120
|
+
| `.sort(by, descending)` | New frame sorted by a column |
|
|
121
|
+
| `.filter(expression)` | New frame filtered by a pandas query expression |
|
|
122
|
+
| `.group(by)` | A `GroupedFrame` for `.sum()` / `.mean()` aggregation |
|
|
123
|
+
| `.pivot(index, columns, values)` | Pivot table |
|
|
124
|
+
| `.join(other, on, how)` | Merge two frames on a shared key |
|
|
125
|
+
| `.export(path)` | Write to CSV, XLSX, JSON, or Parquet |
|
|
126
|
+
|
|
127
|
+
### Visualization
|
|
128
|
+
|
|
129
|
+
| Method | Description |
|
|
130
|
+
|--------|-------------|
|
|
131
|
+
| `.bar(x, y, save)` | Bar chart |
|
|
132
|
+
| `.line(x, y, save)` | Line chart |
|
|
133
|
+
| `.scatter(x, y, save)` | Scatter plot |
|
|
134
|
+
| `.hist(col, save)` | Histogram |
|
|
135
|
+
| `.box(x, y, save)` | Box plot |
|
|
136
|
+
| `.heatmap(save)` | Correlation heatmap |
|
|
137
|
+
| `.pairplot(save)` | Pairwise scatter matrix |
|
|
138
|
+
| `.chart(description)` | Smart chart from plain English |
|
|
139
|
+
|
|
140
|
+
### Analytics
|
|
141
|
+
|
|
142
|
+
| Method | Description |
|
|
143
|
+
|--------|-------------|
|
|
144
|
+
| `.insights()` | Rule-based observations (list of strings) |
|
|
145
|
+
| `.stats.describe()` | Descriptive statistics (DataFrame) |
|
|
146
|
+
| `.stats.correlation()` | Correlation matrix (DataFrame) |
|
|
147
|
+
| `.stats.covariance()` | Covariance matrix (DataFrame) |
|
|
148
|
+
| `.stats.outliers(col)` | IQR outlier detection |
|
|
149
|
+
| `.stats.mean(col)` | Mean of a column |
|
|
150
|
+
| `.stats.median(col)` | Median of a column |
|
|
151
|
+
| `.stats.std(col)` | Standard deviation |
|
|
152
|
+
| `.stats.min(col)` | Minimum value |
|
|
153
|
+
| `.stats.max(col)` | Maximum value |
|
|
154
|
+
| `.stats.quantile(col, q)` | Quantile value |
|
|
155
|
+
| `.stats.value_counts(col)` | Frequency counts (Series) |
|
|
156
|
+
|
|
157
|
+
### SQL
|
|
158
|
+
|
|
159
|
+
| Method | Description |
|
|
160
|
+
|--------|-------------|
|
|
161
|
+
| `.sql(query)` | Run read-only SQL via DuckDB (table name: `data`) |
|
|
162
|
+
|
|
163
|
+
### Reports
|
|
164
|
+
|
|
165
|
+
| Method | Description |
|
|
166
|
+
|--------|-------------|
|
|
167
|
+
| `.report(path)` | Generate HTML or Excel report |
|
|
168
|
+
|
|
169
|
+
## Examples
|
|
170
|
+
|
|
171
|
+
See the [`examples/`](examples/) directory for runnable scripts:
|
|
172
|
+
|
|
173
|
+
| Script | What it shows |
|
|
174
|
+
|--------|---------------|
|
|
175
|
+
| `basic_usage.py` | Core workflow: load → inspect → clean → chart |
|
|
176
|
+
| `exploration_demo.py` | All exploration methods |
|
|
177
|
+
| `cleaning_demo.py` | Cleaning pipeline + report |
|
|
178
|
+
| `transform_demo.py` | Select, filter, group, sort, pivot, chain |
|
|
179
|
+
| `charts_demo.py` | All chart types + smart chart |
|
|
180
|
+
| `analytics_demo.py` | Insights + statistics |
|
|
181
|
+
| `sql_demo.py` | SQL queries + chaining |
|
|
182
|
+
| `report_demo.py` | HTML + Excel reports |
|
|
183
|
+
|
|
184
|
+
## What Tablofy is not
|
|
185
|
+
|
|
186
|
+
- **Not a replacement for pandas** — you can always get the underlying DataFrame with `.to_pandas()`
|
|
187
|
+
- **Not a big-data tool** — it operates in-memory on a single machine
|
|
188
|
+
- **Not a machine-learning library** — no model training or prediction
|
|
189
|
+
- **Not a dashboard tool** — reports are static HTML or Excel files
|
|
190
|
+
- **Not a database** — SQL runs in-memory against the loaded DataFrame only
|
|
191
|
+
|
|
192
|
+
## Roadmap
|
|
193
|
+
|
|
194
|
+
- [x] Load CSV, Excel, JSON, Parquet
|
|
195
|
+
- [x] Data exploration (profile, summary, missing, types)
|
|
196
|
+
- [x] Data cleaning (duplicates, missing values, column names, dates)
|
|
197
|
+
- [x] Transformations (select, drop, sort, filter, group, join)
|
|
198
|
+
- [x] Visualization (bar, line, scatter, hist, box, heatmap, pairplot)
|
|
199
|
+
- [x] Smart chart from plain-English descriptions
|
|
200
|
+
- [x] Rule-based insights
|
|
201
|
+
- [x] Statistical helpers (mean, median, outliers, correlation)
|
|
202
|
+
- [x] SQL queries via DuckDB
|
|
203
|
+
- [x] HTML and Excel reports
|
|
204
|
+
- [ ] Interactive charts with Plotly
|
|
205
|
+
- [ ] Time-series analysis helpers
|
|
206
|
+
- [ ] More chart types (area, pie, violin)
|
|
207
|
+
- [ ] Custom theme/style configuration
|
|
208
|
+
- [ ] Integration with Jupyter Notebook widgets
|
|
209
|
+
|
|
210
|
+
## Contributing
|
|
211
|
+
|
|
212
|
+
Contributions are welcome! Open an issue or pull request on GitHub.
|
|
213
|
+
|
|
214
|
+
## License
|
|
215
|
+
|
|
216
|
+
MIT
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tablofy"
|
|
7
|
+
version = "1.0.0-alpha"
|
|
8
|
+
description = "A beginner-friendly data analytics library wrapping pandas, matplotlib, seaborn, and DuckDB."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "Tablofy Contributors" },
|
|
13
|
+
]
|
|
14
|
+
requires-python = ">=3.9"
|
|
15
|
+
dependencies = [
|
|
16
|
+
"pandas>=2.0",
|
|
17
|
+
"numpy>=1.24",
|
|
18
|
+
"matplotlib>=3.7",
|
|
19
|
+
"seaborn>=0.12",
|
|
20
|
+
"plotly>=5.14",
|
|
21
|
+
"duckdb>=0.8",
|
|
22
|
+
"pyarrow>=12",
|
|
23
|
+
"openpyxl>=3.1",
|
|
24
|
+
"jinja2>=3.1",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.optional-dependencies]
|
|
28
|
+
dev = [
|
|
29
|
+
"pytest>=7",
|
|
30
|
+
"ruff>=0.1",
|
|
31
|
+
"black>=23",
|
|
32
|
+
"build>=1.0",
|
|
33
|
+
"twine>=4.0",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[tool.setuptools.packages.find]
|
|
37
|
+
where = ["src"]
|
|
38
|
+
|
|
39
|
+
[tool.ruff]
|
|
40
|
+
line-length = 100
|
|
41
|
+
target-version = "py39"
|
|
42
|
+
|
|
43
|
+
[tool.ruff.lint]
|
|
44
|
+
select = ["E", "F", "W", "I"]
|
|
45
|
+
|
|
46
|
+
[tool.black]
|
|
47
|
+
line-length = 100
|
|
48
|
+
target-version = ["py39"]
|
|
49
|
+
|
|
50
|
+
[tool.pytest.ini_options]
|
|
51
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"""Tablofy — A beginner-friendly data analytics library."""
|
|
2
|
+
|
|
3
|
+
__version__ = "1.0.0-alpha"
|
|
4
|
+
|
|
5
|
+
from tablofy.core.errors import (
|
|
6
|
+
TablofyColumnError,
|
|
7
|
+
TablofyDataError,
|
|
8
|
+
TablofyError,
|
|
9
|
+
TablofyFileError,
|
|
10
|
+
)
|
|
11
|
+
from tablofy.core.frame import TablofyFrame
|
|
12
|
+
from tablofy.core.loader import load
|
|
13
|
+
|
|
14
|
+
ColumnNotFoundError = TablofyColumnError
|
|
15
|
+
EmptyTableError = TablofyDataError
|
|
16
|
+
FileFormatError = TablofyFileError
|
|
17
|
+
|
|
18
|
+
__all__ = [
|
|
19
|
+
"TablofyFrame",
|
|
20
|
+
"load",
|
|
21
|
+
"TablofyError",
|
|
22
|
+
"TablofyFileError",
|
|
23
|
+
"TablofyColumnError",
|
|
24
|
+
"TablofyDataError",
|
|
25
|
+
"ColumnNotFoundError",
|
|
26
|
+
"EmptyTableError",
|
|
27
|
+
"FileFormatError",
|
|
28
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Analytics: insights, statistics, outlier detection."""
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""Rule-based insight generation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from tablofy.core.frame import TablofyFrame
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Insights:
|
|
12
|
+
"""Generates deterministic, rule-based observations about a dataset."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, frame: TablofyFrame) -> None:
|
|
15
|
+
self._frame = frame
|
|
16
|
+
self._df = frame._df
|
|
17
|
+
|
|
18
|
+
def generate(self) -> list[str]:
|
|
19
|
+
"""Return a list of human-readable insight strings.
|
|
20
|
+
|
|
21
|
+
Checks performed
|
|
22
|
+
-----------------
|
|
23
|
+
- Dataset dimensions
|
|
24
|
+
- Missing values
|
|
25
|
+
- Duplicate rows
|
|
26
|
+
- Strong correlations
|
|
27
|
+
- High-cardinality columns
|
|
28
|
+
- Low-cardinality categorical columns
|
|
29
|
+
- Highest/lowest numeric values
|
|
30
|
+
- Outlier hints
|
|
31
|
+
"""
|
|
32
|
+
insights: list[str] = []
|
|
33
|
+
df = self._df
|
|
34
|
+
|
|
35
|
+
rows, cols = df.shape
|
|
36
|
+
insights.append(f"Dataset has {rows} rows and {cols} columns.")
|
|
37
|
+
|
|
38
|
+
# Missing values
|
|
39
|
+
missing_counts = df.isna().sum()
|
|
40
|
+
total_missing = int(missing_counts.sum())
|
|
41
|
+
if total_missing:
|
|
42
|
+
pct = total_missing / df.size * 100
|
|
43
|
+
insights.append(
|
|
44
|
+
f"Found {total_missing} missing cell(s) ({pct:.1f}% of data)."
|
|
45
|
+
)
|
|
46
|
+
cols_with_missing = [c for c in df.columns if missing_counts[c] > 0]
|
|
47
|
+
insights.append(
|
|
48
|
+
f"Columns with missing values: {cols_with_missing}."
|
|
49
|
+
)
|
|
50
|
+
else:
|
|
51
|
+
insights.append("No missing values detected.")
|
|
52
|
+
|
|
53
|
+
# Duplicate rows
|
|
54
|
+
dup_count = int(df.duplicated().sum())
|
|
55
|
+
if dup_count:
|
|
56
|
+
dup_pct = dup_count / rows * 100
|
|
57
|
+
insights.append(
|
|
58
|
+
f"Found {dup_count} duplicate row(s) ({dup_pct:.1f}% of rows)."
|
|
59
|
+
)
|
|
60
|
+
else:
|
|
61
|
+
insights.append("No duplicate rows found.")
|
|
62
|
+
|
|
63
|
+
# Strong correlations
|
|
64
|
+
numeric = df.select_dtypes(include="number")
|
|
65
|
+
if numeric.shape[1] >= 2:
|
|
66
|
+
corr = numeric.corr().abs()
|
|
67
|
+
strong: list[str] = []
|
|
68
|
+
for i in range(len(corr.columns)):
|
|
69
|
+
for j in range(i + 1, len(corr.columns)):
|
|
70
|
+
val = corr.iloc[i, j]
|
|
71
|
+
if val >= 0.7:
|
|
72
|
+
strong.append(
|
|
73
|
+
f"{corr.columns[i]} — {corr.columns[j]} "
|
|
74
|
+
f"(r = {corr.iloc[i, j]:.2f})"
|
|
75
|
+
)
|
|
76
|
+
if strong:
|
|
77
|
+
insights.append(
|
|
78
|
+
f"Strong correlations detected ({len(strong)}): "
|
|
79
|
+
f"{'; '.join(strong)}."
|
|
80
|
+
)
|
|
81
|
+
else:
|
|
82
|
+
insights.append(
|
|
83
|
+
"No strong correlations (|r| >= 0.7) found between numeric columns."
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# High-cardinality columns
|
|
87
|
+
high_card = [
|
|
88
|
+
c for c in df.columns
|
|
89
|
+
if df[c].nunique() > 50 and df[c].nunique() == len(df)
|
|
90
|
+
]
|
|
91
|
+
if high_card:
|
|
92
|
+
insights.append(
|
|
93
|
+
f"High-cardinality columns (unique ID-like): {high_card}."
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Low-cardinality categorical columns
|
|
97
|
+
obj_cols = df.select_dtypes(include=["object", "category"]).columns
|
|
98
|
+
low_card = [c for c in obj_cols if 2 <= df[c].nunique() <= 10]
|
|
99
|
+
if low_card:
|
|
100
|
+
insights.append(
|
|
101
|
+
f"Low-cardinality categorical columns ({len(low_card)}): "
|
|
102
|
+
f"{low_card}."
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
# Highest / lowest numeric
|
|
106
|
+
if numeric.shape[1] >= 1:
|
|
107
|
+
for col in numeric.columns:
|
|
108
|
+
max_val = df[col].max()
|
|
109
|
+
min_val = df[col].min()
|
|
110
|
+
insights.append(
|
|
111
|
+
f"{col}: min = {min_val}, max = {max_val}."
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# Outlier hints (IQR-based)
|
|
115
|
+
if numeric.shape[1] >= 1:
|
|
116
|
+
for col in numeric.columns:
|
|
117
|
+
q1 = df[col].quantile(0.25)
|
|
118
|
+
q3 = df[col].quantile(0.75)
|
|
119
|
+
iqr = q3 - q1
|
|
120
|
+
lower = q1 - 1.5 * iqr
|
|
121
|
+
upper = q3 + 1.5 * iqr
|
|
122
|
+
outlier_count = int(((df[col] < lower) | (df[col] > upper)).sum())
|
|
123
|
+
if outlier_count:
|
|
124
|
+
pct = outlier_count / rows * 100
|
|
125
|
+
insights.append(
|
|
126
|
+
f"{col}: {outlier_count} potential outlier(s) "
|
|
127
|
+
f"({pct:.1f}% of rows)."
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
return insights
|