tigerdatalab 3.0.4__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.
- tigerdatalab-3.0.4/LICENSE +21 -0
- tigerdatalab-3.0.4/PKG-INFO +235 -0
- tigerdatalab-3.0.4/README.md +195 -0
- tigerdatalab-3.0.4/pyproject.toml +47 -0
- tigerdatalab-3.0.4/setup.cfg +4 -0
- tigerdatalab-3.0.4/tests/test_bugs.py +135 -0
- tigerdatalab-3.0.4/tests/test_core.py +113 -0
- tigerdatalab-3.0.4/tests/test_dataops.py +52 -0
- tigerdatalab-3.0.4/tests/test_extensions.py +80 -0
- tigerdatalab-3.0.4/tests/test_file_locking.py +76 -0
- tigerdatalab-3.0.4/tests/test_large.py +27 -0
- tigerdatalab-3.0.4/tests/test_loaders.py +47 -0
- tigerdatalab-3.0.4/tests/test_semantic_audit.py +103 -0
- tigerdatalab-3.0.4/tests/test_stress.py +112 -0
- tigerdatalab-3.0.4/tigerdatalab/__init__.py +17 -0
- tigerdatalab-3.0.4/tigerdatalab/analytics/__init__.py +0 -0
- tigerdatalab-3.0.4/tigerdatalab/analytics/category.py +38 -0
- tigerdatalab-3.0.4/tigerdatalab/analytics/customer.py +51 -0
- tigerdatalab-3.0.4/tigerdatalab/analytics/growth.py +86 -0
- tigerdatalab-3.0.4/tigerdatalab/analytics/kpi.py +66 -0
- tigerdatalab-3.0.4/tigerdatalab/analytics/product.py +40 -0
- tigerdatalab-3.0.4/tigerdatalab/analytics/profitability.py +24 -0
- tigerdatalab-3.0.4/tigerdatalab/analytics/trends.py +79 -0
- tigerdatalab-3.0.4/tigerdatalab/cli/__init__.py +0 -0
- tigerdatalab-3.0.4/tigerdatalab/cli/main.py +77 -0
- tigerdatalab-3.0.4/tigerdatalab/config.py +47 -0
- tigerdatalab-3.0.4/tigerdatalab/core.py +319 -0
- tigerdatalab-3.0.4/tigerdatalab/dashboard/__init__.py +0 -0
- tigerdatalab-3.0.4/tigerdatalab/dashboard/builder.py +145 -0
- tigerdatalab-3.0.4/tigerdatalab/dataops/__init__.py +0 -0
- tigerdatalab-3.0.4/tigerdatalab/dataops/asset.py +144 -0
- tigerdatalab-3.0.4/tigerdatalab/exceptions.py +71 -0
- tigerdatalab-3.0.4/tigerdatalab/insights/__init__.py +0 -0
- tigerdatalab-3.0.4/tigerdatalab/insights/engine.py +175 -0
- tigerdatalab-3.0.4/tigerdatalab/insights/questions.py +112 -0
- tigerdatalab-3.0.4/tigerdatalab/io/__init__.py +0 -0
- tigerdatalab-3.0.4/tigerdatalab/io/loaders.py +237 -0
- tigerdatalab-3.0.4/tigerdatalab/quality/__init__.py +0 -0
- tigerdatalab-3.0.4/tigerdatalab/quality/anomalies.py +45 -0
- tigerdatalab-3.0.4/tigerdatalab/quality/cleaning.py +85 -0
- tigerdatalab-3.0.4/tigerdatalab/quality/profiler.py +146 -0
- tigerdatalab-3.0.4/tigerdatalab/quality/types.py +163 -0
- tigerdatalab-3.0.4/tigerdatalab/reporting/__init__.py +0 -0
- tigerdatalab-3.0.4/tigerdatalab/reporting/_safe_io.py +63 -0
- tigerdatalab-3.0.4/tigerdatalab/reporting/exporters.py +34 -0
- tigerdatalab-3.0.4/tigerdatalab/reporting/html.py +44 -0
- tigerdatalab-3.0.4/tigerdatalab/reporting/pdf.py +168 -0
- tigerdatalab-3.0.4/tigerdatalab/reporting/terminal.py +137 -0
- tigerdatalab-3.0.4/tigerdatalab/scale/__init__.py +0 -0
- tigerdatalab-3.0.4/tigerdatalab/scale/duckdb_engine.py +96 -0
- tigerdatalab-3.0.4/tigerdatalab/visualization/__init__.py +0 -0
- tigerdatalab-3.0.4/tigerdatalab/visualization/charts.py +247 -0
- tigerdatalab-3.0.4/tigerdatalab.egg-info/PKG-INFO +235 -0
- tigerdatalab-3.0.4/tigerdatalab.egg-info/SOURCES.txt +56 -0
- tigerdatalab-3.0.4/tigerdatalab.egg-info/dependency_links.txt +1 -0
- tigerdatalab-3.0.4/tigerdatalab.egg-info/entry_points.txt +2 -0
- tigerdatalab-3.0.4/tigerdatalab.egg-info/requires.txt +21 -0
- tigerdatalab-3.0.4/tigerdatalab.egg-info/top_level.txt +1 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Abhi
|
|
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.
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tigerdatalab
|
|
3
|
+
Version: 3.0.4
|
|
4
|
+
Summary: Automated Data Analytics + Data Quality + Visualization + BI + DataOps platform, built on pandas/numpy/duckdb/plotly.
|
|
5
|
+
Author: Abhi
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/abhi15724/tigerdatalab
|
|
8
|
+
Project-URL: Repository, https://github.com/abhi15724/tigerdatalab
|
|
9
|
+
Keywords: data-analysis,business-intelligence,eda,dataops,dashboard,pandas,duckdb,plotly
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: pandas>=1.5
|
|
23
|
+
Requires-Dist: numpy>=1.23
|
|
24
|
+
Requires-Dist: openpyxl>=3.1
|
|
25
|
+
Requires-Dist: plotly>=5.18
|
|
26
|
+
Requires-Dist: charset-normalizer>=3.0
|
|
27
|
+
Provides-Extra: large
|
|
28
|
+
Requires-Dist: duckdb>=0.10; extra == "large"
|
|
29
|
+
Requires-Dist: pyarrow>=14.0; extra == "large"
|
|
30
|
+
Provides-Extra: pdf
|
|
31
|
+
Requires-Dist: reportlab>=4.0; extra == "pdf"
|
|
32
|
+
Provides-Extra: all
|
|
33
|
+
Requires-Dist: duckdb>=0.10; extra == "all"
|
|
34
|
+
Requires-Dist: pyarrow>=14.0; extra == "all"
|
|
35
|
+
Requires-Dist: reportlab>=4.0; extra == "all"
|
|
36
|
+
Provides-Extra: dev
|
|
37
|
+
Requires-Dist: pytest>=7.4; extra == "dev"
|
|
38
|
+
Requires-Dist: pytest-cov>=4.1; extra == "dev"
|
|
39
|
+
Dynamic: license-file
|
|
40
|
+
|
|
41
|
+
# TigerDataLab
|
|
42
|
+
|
|
43
|
+
**TigerDataLab** is an automated Data Analytics + Data Quality + Visualization
|
|
44
|
+
+ Business Intelligence + DataOps layer built **on top of** pandas, numpy,
|
|
45
|
+
duckdb and plotly — not a replacement for them.
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import pandas as pd
|
|
49
|
+
import numpy as np # still there when you want low-level control
|
|
50
|
+
|
|
51
|
+
import tigerdatalab as tdl
|
|
52
|
+
|
|
53
|
+
result = tdl.analyze("sales.xlsx")
|
|
54
|
+
print(result.summary())
|
|
55
|
+
result.report("analysis") # dashboard + PDF + HTML + JSON, all in one call
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
For very large data:
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
data = tdl.large("sales.parquet")
|
|
62
|
+
data.count()
|
|
63
|
+
data.aggregate("category", "SUM(revenue) AS revenue", "SUM(profit) AS profit")
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Install
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install tigerdatalab # core (pandas, numpy, openpyxl, plotly)
|
|
70
|
+
pip install "tigerdatalab[all]" # + duckdb, pyarrow, reportlab (large data + PDF)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Local development:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
git clone <repo>
|
|
77
|
+
cd tigerdatalab
|
|
78
|
+
pip install -e ".[all,dev]"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## What `analyze()` does automatically
|
|
82
|
+
|
|
83
|
+
1. Loads CSV / Excel (.xlsx, .xlsm) / JSON / Parquet / SQL / SQLite / DuckDB files.
|
|
84
|
+
2. Detects column data types (never runs `.quantile()` on boolean columns).
|
|
85
|
+
3. Detects business semantics (revenue, cost, profit, quantity, customer,
|
|
86
|
+
product, category, date, discount, order, price) from column names,
|
|
87
|
+
dtypes and value patterns.
|
|
88
|
+
4. Profiles data quality (missing values, duplicates, outliers, invalid
|
|
89
|
+
dates, negative values where they shouldn't occur) into a 0–100 score.
|
|
90
|
+
5. Cleans conservatively (whitespace, date normalization, numeric coercion,
|
|
91
|
+
duplicate removal) — every operation is logged, nothing is silently
|
|
92
|
+
destroyed.
|
|
93
|
+
6. Calculates business KPIs (revenue, profit, margin, AOV, ASP, customers,
|
|
94
|
+
orders, products, discounts).
|
|
95
|
+
7. Computes a performance trend (daily/monthly, MoM/YoY/rolling average)
|
|
96
|
+
when a date + numeric metric pair exists — and degrades gracefully
|
|
97
|
+
(never crashes) when it doesn't.
|
|
98
|
+
8. Runs category, product, and customer analysis (each degrades gracefully
|
|
99
|
+
with a clear message if the relevant identifier column isn't present —
|
|
100
|
+
it never invents numbers).
|
|
101
|
+
9. Generates a rule-based, evidence-backed business insight engine
|
|
102
|
+
(finding → evidence → impact → recommendation), with no LLM required.
|
|
103
|
+
10. Builds 11 chart types (column, bar, line, pie, donut, histogram,
|
|
104
|
+
scatter, box plot, heatmap, pareto, KPI cards) — each with explicit
|
|
105
|
+
title/x-axis/y-axis/metric/aggregation metadata.
|
|
106
|
+
11. Renders a responsive, interactive Plotly dashboard, a static HTML
|
|
107
|
+
report, a professional PDF business report, and JSON exports.
|
|
108
|
+
|
|
109
|
+
## Public API
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
result = tdl.analyze("sales.xlsx")
|
|
113
|
+
|
|
114
|
+
result.summary() # str
|
|
115
|
+
result.kpis() # dict
|
|
116
|
+
result.quality() # dict
|
|
117
|
+
result.statistics() # dict
|
|
118
|
+
result.trends() # dict
|
|
119
|
+
result.customers() # dict
|
|
120
|
+
result.products() # dict
|
|
121
|
+
result.categories() # dict
|
|
122
|
+
result.insights() # list[dict] — severity/title/evidence/impact/recommendation
|
|
123
|
+
result.recommendations() # list[str]
|
|
124
|
+
result.visualize() # list[ChartSpec] — 12 chart types incl. KPI Cards
|
|
125
|
+
result.growth() # dict — growing/declining products & categories
|
|
126
|
+
result.anomalies() # dict — z-score anomaly rows/columns
|
|
127
|
+
result.ask() # dict — answers to the standard business-question set
|
|
128
|
+
result.ask("which_category_generates_the_most_revenue") # single answer
|
|
129
|
+
|
|
130
|
+
result.dashboard("analysis/dashboard.html")
|
|
131
|
+
result.export("analysis") # cleaned_data.xlsx, insights/quality/statistics/kpis.json
|
|
132
|
+
result.report("analysis") # export() + dashboard.html + analysis_report.html
|
|
133
|
+
# + business_insights.pdf + charts/*.html
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## DataOps — controlled, audited writes
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
data = tdl.open("sales.xlsx")
|
|
140
|
+
|
|
141
|
+
data.update(where={"product_id": "SKU-1"}, values={"price": 499})
|
|
142
|
+
data.insert({"product_id": "SKU-99", "product": "Mouse", "price": 399})
|
|
143
|
+
data.delete(where={"product_id": "SKU-99"})
|
|
144
|
+
data.upsert({"product_id": "SKU-1", "price": 509}, key="product_id")
|
|
145
|
+
|
|
146
|
+
data.rollback() # undo the last operation
|
|
147
|
+
data.save() # write back to the original file
|
|
148
|
+
data.save_audit_log("analysis/audit.json")
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
`update()`/`delete()` raise a clear error (`UpdateMatchedZeroRowsError`,
|
|
152
|
+
`DeleteMatchedZeroRowsError`) instead of silently doing nothing.
|
|
153
|
+
|
|
154
|
+
## Large data (DuckDB-backed, lazy)
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
data = tdl.large("large_sales.parquet") # CSV/Parquet, no full pandas load
|
|
158
|
+
data.count()
|
|
159
|
+
data.aggregate("category", "SUM(revenue) AS revenue")
|
|
160
|
+
data.query("SELECT category, AVG(revenue) FROM data GROUP BY category") # destructive SQL is refused
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## CLI
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
tigerdatalab analyze sales.csv
|
|
167
|
+
tigerdatalab dashboard sales.csv -o analysis/dashboard.html
|
|
168
|
+
tigerdatalab profile sales.csv
|
|
169
|
+
tigerdatalab quality sales.csv
|
|
170
|
+
tigerdatalab clean sales.csv -o cleaned.xlsx
|
|
171
|
+
tigerdatalab report sales.csv -o analysis
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## SQL files
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
result = tdl.analyze("sales.sql") # CREATE TABLE / INSERT INTO / SELECT
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Executed via DuckDB (falls back to sqlite3 if duckdb isn't installed).
|
|
181
|
+
`DROP` / `TRUNCATE` / `DELETE` / `ALTER` are always refused — use the
|
|
182
|
+
DataOps API for explicit, audited writes instead.
|
|
183
|
+
|
|
184
|
+
## Privacy & security
|
|
185
|
+
|
|
186
|
+
Everything runs **locally**. TigerDataLab never uploads your data, never
|
|
187
|
+
calls an external API by default, and never executes arbitrary shell
|
|
188
|
+
commands from a dataset. The business-insight engine is fully deterministic
|
|
189
|
+
and rule-based — no LLM key required. An `InsightProvider` interface exists
|
|
190
|
+
for anyone who wants to plug in an optional LLM-backed provider later.
|
|
191
|
+
|
|
192
|
+
## Project layout
|
|
193
|
+
|
|
194
|
+
```
|
|
195
|
+
tigerdatalab/
|
|
196
|
+
├── core.py # AnalysisResult, analyze(), open(), large()
|
|
197
|
+
├── config.py
|
|
198
|
+
├── exceptions.py
|
|
199
|
+
├── io/loaders.py # csv/excel/json/parquet/sql/sqlite/duckdb loading
|
|
200
|
+
├── quality/ # types.py, profiler.py, cleaning.py
|
|
201
|
+
├── analytics/ # kpi.py, trends.py, customer.py, product.py, category.py, profitability.py
|
|
202
|
+
├── insights/engine.py # rule-based insight generation
|
|
203
|
+
├── visualization/charts.py # chart engine (11 chart types)
|
|
204
|
+
├── dashboard/builder.py # interactive HTML dashboard
|
|
205
|
+
├── reporting/ # html.py, pdf.py, exporters.py
|
|
206
|
+
├── dataops/asset.py # update/insert/delete/upsert/merge/rollback/audit
|
|
207
|
+
├── scale/duckdb_engine.py # lazy large-data aggregation
|
|
208
|
+
└── cli/main.py
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Testing
|
|
212
|
+
|
|
213
|
+
```bash
|
|
214
|
+
python -m pytest
|
|
215
|
+
python examples/sales_analysis.py
|
|
216
|
+
python examples/dataops_example.py
|
|
217
|
+
python examples/large_data_example.py
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
31 tests cover CSV/Excel/Parquet/SQL loading, boolean-safe type detection,
|
|
221
|
+
missing-date/no-trend handling, empty/one-column/categorical-only datasets,
|
|
222
|
+
DataOps (insert/update/delete/upsert/rollback/audit), and DuckDB large-data
|
|
223
|
+
aggregation.
|
|
224
|
+
|
|
225
|
+
## Publishing to PyPI
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
pip install build twine
|
|
229
|
+
python -m build
|
|
230
|
+
twine upload dist/*
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
## Version
|
|
234
|
+
|
|
235
|
+
3.0.0 — see `CHANGELOG.md` for the full list of fixes over the 2.2.0 prototype.
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# TigerDataLab
|
|
2
|
+
|
|
3
|
+
**TigerDataLab** is an automated Data Analytics + Data Quality + Visualization
|
|
4
|
+
+ Business Intelligence + DataOps layer built **on top of** pandas, numpy,
|
|
5
|
+
duckdb and plotly — not a replacement for them.
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
import pandas as pd
|
|
9
|
+
import numpy as np # still there when you want low-level control
|
|
10
|
+
|
|
11
|
+
import tigerdatalab as tdl
|
|
12
|
+
|
|
13
|
+
result = tdl.analyze("sales.xlsx")
|
|
14
|
+
print(result.summary())
|
|
15
|
+
result.report("analysis") # dashboard + PDF + HTML + JSON, all in one call
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
For very large data:
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
data = tdl.large("sales.parquet")
|
|
22
|
+
data.count()
|
|
23
|
+
data.aggregate("category", "SUM(revenue) AS revenue", "SUM(profit) AS profit")
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Install
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install tigerdatalab # core (pandas, numpy, openpyxl, plotly)
|
|
30
|
+
pip install "tigerdatalab[all]" # + duckdb, pyarrow, reportlab (large data + PDF)
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Local development:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
git clone <repo>
|
|
37
|
+
cd tigerdatalab
|
|
38
|
+
pip install -e ".[all,dev]"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## What `analyze()` does automatically
|
|
42
|
+
|
|
43
|
+
1. Loads CSV / Excel (.xlsx, .xlsm) / JSON / Parquet / SQL / SQLite / DuckDB files.
|
|
44
|
+
2. Detects column data types (never runs `.quantile()` on boolean columns).
|
|
45
|
+
3. Detects business semantics (revenue, cost, profit, quantity, customer,
|
|
46
|
+
product, category, date, discount, order, price) from column names,
|
|
47
|
+
dtypes and value patterns.
|
|
48
|
+
4. Profiles data quality (missing values, duplicates, outliers, invalid
|
|
49
|
+
dates, negative values where they shouldn't occur) into a 0–100 score.
|
|
50
|
+
5. Cleans conservatively (whitespace, date normalization, numeric coercion,
|
|
51
|
+
duplicate removal) — every operation is logged, nothing is silently
|
|
52
|
+
destroyed.
|
|
53
|
+
6. Calculates business KPIs (revenue, profit, margin, AOV, ASP, customers,
|
|
54
|
+
orders, products, discounts).
|
|
55
|
+
7. Computes a performance trend (daily/monthly, MoM/YoY/rolling average)
|
|
56
|
+
when a date + numeric metric pair exists — and degrades gracefully
|
|
57
|
+
(never crashes) when it doesn't.
|
|
58
|
+
8. Runs category, product, and customer analysis (each degrades gracefully
|
|
59
|
+
with a clear message if the relevant identifier column isn't present —
|
|
60
|
+
it never invents numbers).
|
|
61
|
+
9. Generates a rule-based, evidence-backed business insight engine
|
|
62
|
+
(finding → evidence → impact → recommendation), with no LLM required.
|
|
63
|
+
10. Builds 11 chart types (column, bar, line, pie, donut, histogram,
|
|
64
|
+
scatter, box plot, heatmap, pareto, KPI cards) — each with explicit
|
|
65
|
+
title/x-axis/y-axis/metric/aggregation metadata.
|
|
66
|
+
11. Renders a responsive, interactive Plotly dashboard, a static HTML
|
|
67
|
+
report, a professional PDF business report, and JSON exports.
|
|
68
|
+
|
|
69
|
+
## Public API
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
result = tdl.analyze("sales.xlsx")
|
|
73
|
+
|
|
74
|
+
result.summary() # str
|
|
75
|
+
result.kpis() # dict
|
|
76
|
+
result.quality() # dict
|
|
77
|
+
result.statistics() # dict
|
|
78
|
+
result.trends() # dict
|
|
79
|
+
result.customers() # dict
|
|
80
|
+
result.products() # dict
|
|
81
|
+
result.categories() # dict
|
|
82
|
+
result.insights() # list[dict] — severity/title/evidence/impact/recommendation
|
|
83
|
+
result.recommendations() # list[str]
|
|
84
|
+
result.visualize() # list[ChartSpec] — 12 chart types incl. KPI Cards
|
|
85
|
+
result.growth() # dict — growing/declining products & categories
|
|
86
|
+
result.anomalies() # dict — z-score anomaly rows/columns
|
|
87
|
+
result.ask() # dict — answers to the standard business-question set
|
|
88
|
+
result.ask("which_category_generates_the_most_revenue") # single answer
|
|
89
|
+
|
|
90
|
+
result.dashboard("analysis/dashboard.html")
|
|
91
|
+
result.export("analysis") # cleaned_data.xlsx, insights/quality/statistics/kpis.json
|
|
92
|
+
result.report("analysis") # export() + dashboard.html + analysis_report.html
|
|
93
|
+
# + business_insights.pdf + charts/*.html
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## DataOps — controlled, audited writes
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
data = tdl.open("sales.xlsx")
|
|
100
|
+
|
|
101
|
+
data.update(where={"product_id": "SKU-1"}, values={"price": 499})
|
|
102
|
+
data.insert({"product_id": "SKU-99", "product": "Mouse", "price": 399})
|
|
103
|
+
data.delete(where={"product_id": "SKU-99"})
|
|
104
|
+
data.upsert({"product_id": "SKU-1", "price": 509}, key="product_id")
|
|
105
|
+
|
|
106
|
+
data.rollback() # undo the last operation
|
|
107
|
+
data.save() # write back to the original file
|
|
108
|
+
data.save_audit_log("analysis/audit.json")
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
`update()`/`delete()` raise a clear error (`UpdateMatchedZeroRowsError`,
|
|
112
|
+
`DeleteMatchedZeroRowsError`) instead of silently doing nothing.
|
|
113
|
+
|
|
114
|
+
## Large data (DuckDB-backed, lazy)
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
data = tdl.large("large_sales.parquet") # CSV/Parquet, no full pandas load
|
|
118
|
+
data.count()
|
|
119
|
+
data.aggregate("category", "SUM(revenue) AS revenue")
|
|
120
|
+
data.query("SELECT category, AVG(revenue) FROM data GROUP BY category") # destructive SQL is refused
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## CLI
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
tigerdatalab analyze sales.csv
|
|
127
|
+
tigerdatalab dashboard sales.csv -o analysis/dashboard.html
|
|
128
|
+
tigerdatalab profile sales.csv
|
|
129
|
+
tigerdatalab quality sales.csv
|
|
130
|
+
tigerdatalab clean sales.csv -o cleaned.xlsx
|
|
131
|
+
tigerdatalab report sales.csv -o analysis
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## SQL files
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
result = tdl.analyze("sales.sql") # CREATE TABLE / INSERT INTO / SELECT
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Executed via DuckDB (falls back to sqlite3 if duckdb isn't installed).
|
|
141
|
+
`DROP` / `TRUNCATE` / `DELETE` / `ALTER` are always refused — use the
|
|
142
|
+
DataOps API for explicit, audited writes instead.
|
|
143
|
+
|
|
144
|
+
## Privacy & security
|
|
145
|
+
|
|
146
|
+
Everything runs **locally**. TigerDataLab never uploads your data, never
|
|
147
|
+
calls an external API by default, and never executes arbitrary shell
|
|
148
|
+
commands from a dataset. The business-insight engine is fully deterministic
|
|
149
|
+
and rule-based — no LLM key required. An `InsightProvider` interface exists
|
|
150
|
+
for anyone who wants to plug in an optional LLM-backed provider later.
|
|
151
|
+
|
|
152
|
+
## Project layout
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
tigerdatalab/
|
|
156
|
+
├── core.py # AnalysisResult, analyze(), open(), large()
|
|
157
|
+
├── config.py
|
|
158
|
+
├── exceptions.py
|
|
159
|
+
├── io/loaders.py # csv/excel/json/parquet/sql/sqlite/duckdb loading
|
|
160
|
+
├── quality/ # types.py, profiler.py, cleaning.py
|
|
161
|
+
├── analytics/ # kpi.py, trends.py, customer.py, product.py, category.py, profitability.py
|
|
162
|
+
├── insights/engine.py # rule-based insight generation
|
|
163
|
+
├── visualization/charts.py # chart engine (11 chart types)
|
|
164
|
+
├── dashboard/builder.py # interactive HTML dashboard
|
|
165
|
+
├── reporting/ # html.py, pdf.py, exporters.py
|
|
166
|
+
├── dataops/asset.py # update/insert/delete/upsert/merge/rollback/audit
|
|
167
|
+
├── scale/duckdb_engine.py # lazy large-data aggregation
|
|
168
|
+
└── cli/main.py
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Testing
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
python -m pytest
|
|
175
|
+
python examples/sales_analysis.py
|
|
176
|
+
python examples/dataops_example.py
|
|
177
|
+
python examples/large_data_example.py
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
31 tests cover CSV/Excel/Parquet/SQL loading, boolean-safe type detection,
|
|
181
|
+
missing-date/no-trend handling, empty/one-column/categorical-only datasets,
|
|
182
|
+
DataOps (insert/update/delete/upsert/rollback/audit), and DuckDB large-data
|
|
183
|
+
aggregation.
|
|
184
|
+
|
|
185
|
+
## Publishing to PyPI
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
pip install build twine
|
|
189
|
+
python -m build
|
|
190
|
+
twine upload dist/*
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Version
|
|
194
|
+
|
|
195
|
+
3.0.0 — see `CHANGELOG.md` for the full list of fixes over the 2.2.0 prototype.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "tigerdatalab"
|
|
7
|
+
version = "3.0.4"
|
|
8
|
+
description = "Automated Data Analytics + Data Quality + Visualization + BI + DataOps platform, built on pandas/numpy/duckdb/plotly."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [{ name = "Abhi" }]
|
|
13
|
+
keywords = ["data-analysis", "business-intelligence", "eda", "dataops", "dashboard", "pandas", "duckdb", "plotly"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Intended Audience :: Developers",
|
|
17
|
+
"Intended Audience :: Science/Research",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python :: 3.10",
|
|
20
|
+
"Programming Language :: Python :: 3.11",
|
|
21
|
+
"Programming Language :: Python :: 3.12",
|
|
22
|
+
"Programming Language :: Python :: 3.13",
|
|
23
|
+
"Topic :: Scientific/Engineering :: Information Analysis",
|
|
24
|
+
]
|
|
25
|
+
dependencies = [
|
|
26
|
+
"pandas>=1.5",
|
|
27
|
+
"numpy>=1.23",
|
|
28
|
+
"openpyxl>=3.1",
|
|
29
|
+
"plotly>=5.18",
|
|
30
|
+
"charset-normalizer>=3.0",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
large = ["duckdb>=0.10", "pyarrow>=14.0"]
|
|
35
|
+
pdf = ["reportlab>=4.0"]
|
|
36
|
+
all = ["duckdb>=0.10", "pyarrow>=14.0", "reportlab>=4.0"]
|
|
37
|
+
dev = ["pytest>=7.4", "pytest-cov>=4.1"]
|
|
38
|
+
|
|
39
|
+
[project.urls]
|
|
40
|
+
Homepage = "https://github.com/abhi15724/tigerdatalab"
|
|
41
|
+
Repository = "https://github.com/abhi15724/tigerdatalab"
|
|
42
|
+
|
|
43
|
+
[project.scripts]
|
|
44
|
+
tigerdatalab = "tigerdatalab.cli.main:main"
|
|
45
|
+
|
|
46
|
+
[tool.setuptools.packages.find]
|
|
47
|
+
include = ["tigerdatalab*"]
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""Regression tests for the specific bugs the spec calls out by name."""
|
|
2
|
+
import pandas as pd
|
|
3
|
+
import pytest
|
|
4
|
+
|
|
5
|
+
import tigerdatalab as tdl
|
|
6
|
+
from tigerdatalab.quality.types import detect_dtype, safe_quantile, detect_all_dtypes
|
|
7
|
+
from tigerdatalab.quality.profiler import outlier_report
|
|
8
|
+
from tigerdatalab.exceptions import EmptyDatasetError, UnsupportedFileTypeError
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_summary_attribute_exists(csv_path):
|
|
12
|
+
result = tdl.analyze(str(csv_path), verbose=False)
|
|
13
|
+
assert hasattr(result, "summary")
|
|
14
|
+
assert isinstance(result.summary(), str)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_boolean_column_never_quantiled(sales_df):
|
|
18
|
+
# in_stock is a real boolean column in the fixture
|
|
19
|
+
assert detect_dtype(sales_df["in_stock"]) == "boolean"
|
|
20
|
+
assert safe_quantile(sales_df["in_stock"], 0.5) is None
|
|
21
|
+
|
|
22
|
+
dtypes = detect_all_dtypes(sales_df)
|
|
23
|
+
report = outlier_report(sales_df, dtypes)
|
|
24
|
+
assert "in_stock" not in report["by_column"]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_output_paths_are_paths_not_dicts(csv_path, tmp_path):
|
|
28
|
+
result = tdl.analyze(str(csv_path), verbose=False)
|
|
29
|
+
outputs = result.export(tmp_path / "analysis")
|
|
30
|
+
for key, path in outputs.items():
|
|
31
|
+
assert hasattr(path, "exists"), f"{key} output should be a Path-like object"
|
|
32
|
+
assert path.exists()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def test_messy_dates_do_not_crash(messy_df, tmp_path):
|
|
36
|
+
p = tmp_path / "messy.csv"
|
|
37
|
+
messy_df.to_csv(p, index=False)
|
|
38
|
+
result = tdl.analyze(str(p), verbose=False)
|
|
39
|
+
assert result.profile["rows"] >= 1
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_missing_trend_does_not_crash_dashboard(no_date_df, tmp_path):
|
|
43
|
+
p = tmp_path / "no_date.csv"
|
|
44
|
+
no_date_df.to_csv(p, index=False)
|
|
45
|
+
result = tdl.analyze(str(p), verbose=False)
|
|
46
|
+
out = result.dashboard(tmp_path / "dashboard.html")
|
|
47
|
+
html = out.read_text()
|
|
48
|
+
assert "Not enough data" in html
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_multiple_chart_types_present(csv_path):
|
|
52
|
+
result = tdl.analyze(str(csv_path), verbose=False)
|
|
53
|
+
keys = {s.key for s in result.visualize()}
|
|
54
|
+
# more than "only one chart type"
|
|
55
|
+
assert len({"performance_trend", "category_pie", "product_bar", "correlation_heatmap"} & keys) == 4
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_every_chart_has_explanatory_metadata(csv_path):
|
|
59
|
+
result = tdl.analyze(str(csv_path), verbose=False)
|
|
60
|
+
for spec in result.visualize():
|
|
61
|
+
meta = spec.to_meta()
|
|
62
|
+
assert meta["title"]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_empty_dataset_raises_clear_error(tmp_path):
|
|
66
|
+
p = tmp_path / "empty.csv"
|
|
67
|
+
pd.DataFrame({"a": [], "b": []}).to_csv(p, index=False)
|
|
68
|
+
with pytest.raises(EmptyDatasetError):
|
|
69
|
+
tdl.analyze(str(p), verbose=False)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_unsupported_extension_raises_clear_error(tmp_path):
|
|
73
|
+
p = tmp_path / "file.abc"
|
|
74
|
+
p.write_text("nothing")
|
|
75
|
+
with pytest.raises(UnsupportedFileTypeError):
|
|
76
|
+
tdl.analyze(str(p), verbose=False)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def test_one_column_numeric_only_dataset(tmp_path):
|
|
80
|
+
p = tmp_path / "single.csv"
|
|
81
|
+
pd.DataFrame({"revenue": [1, 2, 3, 4, 5]}).to_csv(p, index=False)
|
|
82
|
+
result = tdl.analyze(str(p), verbose=False)
|
|
83
|
+
assert result.kpis().get("total_revenue") == 15
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def test_camelcase_columns_detected_correctly(tmp_path):
|
|
87
|
+
# Reproduces: a catalog-style CSV with camelCase headers (mrp,
|
|
88
|
+
# discountPercent, discountedSellingPrice, name, Category ...) failed
|
|
89
|
+
# to detect the plain "name" column as a product identifier, so
|
|
90
|
+
# result.products() incorrectly reported "not available".
|
|
91
|
+
p = tmp_path / "catalog.csv"
|
|
92
|
+
p.write_text(
|
|
93
|
+
"Category,name,mrp,discountPercent,discountedSellingPrice,quantity\n"
|
|
94
|
+
"Snacks,Choco Bar,100,10,90,5\n"
|
|
95
|
+
"Snacks,Wafer Pack,80,20,64,10\n"
|
|
96
|
+
"Beverages,Cola Can,50,5,47.5,20\n"
|
|
97
|
+
)
|
|
98
|
+
result = tdl.analyze(str(p), verbose=False)
|
|
99
|
+
assert result.semantics.get("product") == "name"
|
|
100
|
+
assert result.products()["available"] is True
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def test_print_table_runs_without_error(capsys):
|
|
104
|
+
result = tdl.analyze("tests/data/finance.csv", verbose=False)
|
|
105
|
+
result.print_table()
|
|
106
|
+
captured = capsys.readouterr()
|
|
107
|
+
assert "DATASET OVERVIEW" in captured.out
|
|
108
|
+
assert "BUSINESS KPIs" in captured.out
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def test_non_utf8_csv_does_not_crash(tmp_path):
|
|
112
|
+
# Reproduces: UnicodeDecodeError: 'utf-8' codec can't decode byte 0x92
|
|
113
|
+
# This happens on real-world CSVs exported from Excel/Windows, which
|
|
114
|
+
# are commonly cp1252 and contain "smart quote" characters (0x91-0x94).
|
|
115
|
+
p = tmp_path / "windows_export.csv"
|
|
116
|
+
with open(p, "wb") as f:
|
|
117
|
+
f.write("order_id,customer_name,notes,revenue\n".encode("utf-8"))
|
|
118
|
+
f.write(
|
|
119
|
+
("1,O'Brien,Client said " + chr(0x2019) + "great job" + chr(0x2019)
|
|
120
|
+
+ " today,100.0\n").encode("cp1252")
|
|
121
|
+
)
|
|
122
|
+
f.write("2,Smith,ok,200.5\n".encode("utf-8"))
|
|
123
|
+
f.write("3,Lee,fine,150.25\n".encode("utf-8"))
|
|
124
|
+
|
|
125
|
+
result = tdl.analyze(str(p), verbose=False)
|
|
126
|
+
assert result.profile["rows"] == 3
|
|
127
|
+
assert result.load_meta["encoding"] != "utf-8"
|
|
128
|
+
assert "\u2019" in result.cleaned_df["notes"].iloc[0]
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def test_categorical_only_dataset(tmp_path):
|
|
132
|
+
p = tmp_path / "cat_only.csv"
|
|
133
|
+
pd.DataFrame({"category": ["A", "B", "A", "C"], "region": ["N", "S", "E", "W"]}).to_csv(p, index=False)
|
|
134
|
+
result = tdl.analyze(str(p), verbose=False)
|
|
135
|
+
assert result.profile["rows"] == 4
|