tangra-dataforge 0.1.0a1__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.
- tangra_dataforge-0.1.0a1/.github/workflows/release.yml +53 -0
- tangra_dataforge-0.1.0a1/.github/workflows/tests.yml +33 -0
- tangra_dataforge-0.1.0a1/.gitignore +9 -0
- tangra_dataforge-0.1.0a1/LICENSE +21 -0
- tangra_dataforge-0.1.0a1/PKG-INFO +131 -0
- tangra_dataforge-0.1.0a1/README.md +99 -0
- tangra_dataforge-0.1.0a1/pyproject.toml +56 -0
- tangra_dataforge-0.1.0a1/src/tangra_dataforge/__init__.py +0 -0
- tangra_dataforge-0.1.0a1/src/tangra_dataforge/analyze.py +83 -0
- tangra_dataforge-0.1.0a1/src/tangra_dataforge/browser.py +0 -0
- tangra_dataforge-0.1.0a1/src/tangra_dataforge/clean.py +13 -0
- tangra_dataforge-0.1.0a1/src/tangra_dataforge/collect.py +66 -0
- tangra_dataforge-0.1.0a1/src/tangra_dataforge/database.py +48 -0
- tangra_dataforge-0.1.0a1/src/tangra_dataforge/geospatial.py +0 -0
- tangra_dataforge-0.1.0a1/src/tangra_dataforge/interactive.py +77 -0
- tangra_dataforge-0.1.0a1/src/tangra_dataforge/pipeline.py +121 -0
- tangra_dataforge-0.1.0a1/src/tangra_dataforge/provenance.py +53 -0
- tangra_dataforge-0.1.0a1/src/tangra_dataforge/scrape.py +90 -0
- tangra_dataforge-0.1.0a1/src/tangra_dataforge/statistics.py +158 -0
- tangra_dataforge-0.1.0a1/src/tangra_dataforge/validate.py +128 -0
- tangra_dataforge-0.1.0a1/src/tangra_dataforge/visualize.py +90 -0
- tangra_dataforge-0.1.0a1/tests/__init__.py +0 -0
- tangra_dataforge-0.1.0a1/tests/test_analyze.py +88 -0
- tangra_dataforge-0.1.0a1/tests/test_browser.py +0 -0
- tangra_dataforge-0.1.0a1/tests/test_clean.py +25 -0
- tangra_dataforge-0.1.0a1/tests/test_collect.py +93 -0
- tangra_dataforge-0.1.0a1/tests/test_database.py +66 -0
- tangra_dataforge-0.1.0a1/tests/test_geospatial.py +0 -0
- tangra_dataforge-0.1.0a1/tests/test_interactive.py +70 -0
- tangra_dataforge-0.1.0a1/tests/test_pipeline.py +110 -0
- tangra_dataforge-0.1.0a1/tests/test_provenance.py +45 -0
- tangra_dataforge-0.1.0a1/tests/test_scrape.py +87 -0
- tangra_dataforge-0.1.0a1/tests/test_statistics.py +91 -0
- tangra_dataforge-0.1.0a1/tests/test_validate.py +118 -0
- tangra_dataforge-0.1.0a1/tests/test_visualize.py +59 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
build:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- uses: actions/setup-python@v5
|
|
16
|
+
with:
|
|
17
|
+
python-version: "3.14"
|
|
18
|
+
|
|
19
|
+
- name: Install build tools
|
|
20
|
+
run: python -m pip install build twine
|
|
21
|
+
|
|
22
|
+
- name: Build distributions
|
|
23
|
+
run: python -m build
|
|
24
|
+
|
|
25
|
+
- name: Verify distributions
|
|
26
|
+
run: python -m twine check dist/*
|
|
27
|
+
|
|
28
|
+
- name: Upload distributions
|
|
29
|
+
uses: actions/upload-artifact@v4
|
|
30
|
+
with:
|
|
31
|
+
name: python-package-distributions
|
|
32
|
+
path: dist/
|
|
33
|
+
|
|
34
|
+
publish:
|
|
35
|
+
needs: build
|
|
36
|
+
runs-on: ubuntu-latest
|
|
37
|
+
|
|
38
|
+
environment:
|
|
39
|
+
name: pypi
|
|
40
|
+
url: https://pypi.org/project/tangra-dataforge/
|
|
41
|
+
|
|
42
|
+
permissions:
|
|
43
|
+
id-token: write
|
|
44
|
+
|
|
45
|
+
steps:
|
|
46
|
+
- name: Download distributions
|
|
47
|
+
uses: actions/download-artifact@v4
|
|
48
|
+
with:
|
|
49
|
+
name: python-package-distributions
|
|
50
|
+
path: dist/
|
|
51
|
+
|
|
52
|
+
- name: Publish to PyPI
|
|
53
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
name: Python Tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
|
|
12
|
+
strategy:
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.11", "3.14"]
|
|
15
|
+
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
|
|
19
|
+
- name: Set up Python
|
|
20
|
+
uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: ${{ matrix.python-version }}
|
|
23
|
+
|
|
24
|
+
- name: Install dependencies
|
|
25
|
+
run: |
|
|
26
|
+
python -m pip install --upgrade pip
|
|
27
|
+
python -m pip install -e ".[dev,scraping,analytics,visualization]"
|
|
28
|
+
|
|
29
|
+
- name: Run tests
|
|
30
|
+
run: python -m pytest -v
|
|
31
|
+
|
|
32
|
+
- name: Verify dependencies
|
|
33
|
+
run: python -m pip check
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Irena Popova
|
|
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
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
|
16
|
+
OR 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
|
|
21
|
+
THE SOFTWARE.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: tangra-dataforge
|
|
3
|
+
Version: 0.1.0a1
|
|
4
|
+
Summary: Python toolkit for data collection, cleaning, analysis, and investigative journalism.
|
|
5
|
+
Author: Irena Popova
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Requires-Python: >=3.11
|
|
9
|
+
Requires-Dist: matplotlib>=3.8
|
|
10
|
+
Requires-Dist: openpyxl>=3.1
|
|
11
|
+
Requires-Dist: pandas>=2.0
|
|
12
|
+
Requires-Dist: requests>=2.31
|
|
13
|
+
Provides-Extra: analytics
|
|
14
|
+
Requires-Dist: duckdb>=1.1; extra == 'analytics'
|
|
15
|
+
Requires-Dist: pyarrow>=17.0; extra == 'analytics'
|
|
16
|
+
Requires-Dist: scipy>=1.13; extra == 'analytics'
|
|
17
|
+
Provides-Extra: dev
|
|
18
|
+
Requires-Dist: build>=1.0; extra == 'dev'
|
|
19
|
+
Requires-Dist: mypy>=1.14; extra == 'dev'
|
|
20
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
21
|
+
Requires-Dist: ruff>=0.9; extra == 'dev'
|
|
22
|
+
Provides-Extra: scraping
|
|
23
|
+
Requires-Dist: beautifulsoup4>=4.12; extra == 'scraping'
|
|
24
|
+
Requires-Dist: httpx>=0.27; extra == 'scraping'
|
|
25
|
+
Requires-Dist: lxml>=5.0; extra == 'scraping'
|
|
26
|
+
Requires-Dist: playwright>=1.50; extra == 'scraping'
|
|
27
|
+
Requires-Dist: scrapy>=2.12; extra == 'scraping'
|
|
28
|
+
Provides-Extra: visualization
|
|
29
|
+
Requires-Dist: geopandas>=1.0; extra == 'visualization'
|
|
30
|
+
Requires-Dist: plotly>=5.24; extra == 'visualization'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# TANGRA DataForge
|
|
34
|
+
|
|
35
|
+
**Open-Source Python Toolkit for Data Engineering, Analysis & Investigative Journalism**
|
|
36
|
+
|
|
37
|
+
TANGRA DataForge is an open-source Python project focused on collecting, processing, analyzing, and visualizing data. It is designed for data journalism, investigative research, and reproducible data analysis.
|
|
38
|
+
|
|
39
|
+
The project aims to transform raw data from public datasets, websites, and APIs into structured, verifiable, and meaningful information.
|
|
40
|
+
|
|
41
|
+
## Project Goals
|
|
42
|
+
|
|
43
|
+
- Collect data from public APIs, websites, CSV, JSON, and Excel files.
|
|
44
|
+
- Extract structured information through web scraping.
|
|
45
|
+
- Clean, validate, and transform datasets using Python and Pandas.
|
|
46
|
+
- Perform statistical analysis and identify patterns, trends, and anomalies.
|
|
47
|
+
- Create informative visualizations and charts.
|
|
48
|
+
- Support reproducible investigations with transparent methodologies and documented sources.
|
|
49
|
+
|
|
50
|
+
## Technology Stack
|
|
51
|
+
|
|
52
|
+
- **Python** — Core programming language
|
|
53
|
+
- **Pandas** — Data manipulation and analysis
|
|
54
|
+
- **NumPy** — Numerical computing
|
|
55
|
+
- **Beautiful Soup** — HTML parsing and web scraping
|
|
56
|
+
- **Requests** — HTTP requests and API integration
|
|
57
|
+
- **Matplotlib / Plotly** — Data visualization
|
|
58
|
+
- **pytest** — Automated testing
|
|
59
|
+
- **Git / GitHub** — Version control and collaboration
|
|
60
|
+
|
|
61
|
+
These technologies represent the planned toolkit. Dependencies will be introduced as features are implemented.
|
|
62
|
+
|
|
63
|
+
## Planned Architecture
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
tangra-dataforge/
|
|
67
|
+
├── src/
|
|
68
|
+
│ └── tangra_dataforge/
|
|
69
|
+
│ ├── __init__.py
|
|
70
|
+
│ ├── collect.py
|
|
71
|
+
│ ├── clean.py
|
|
72
|
+
│ ├── analyze.py
|
|
73
|
+
│ └── visualize.py
|
|
74
|
+
├── investigations/
|
|
75
|
+
├── examples/
|
|
76
|
+
├── tests/
|
|
77
|
+
├── pyproject.toml
|
|
78
|
+
├── README.md
|
|
79
|
+
└── .gitignore
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Data Journalism & Research
|
|
83
|
+
|
|
84
|
+
TANGRA DataForge is being developed to support data-driven investigations into economic, social, political, and technological developments.
|
|
85
|
+
|
|
86
|
+
Planned research applications include:
|
|
87
|
+
|
|
88
|
+
- Economic indicators and regional inequality
|
|
89
|
+
- Housing affordability and income development
|
|
90
|
+
- Labour markets and workforce shortages
|
|
91
|
+
- Education and professional qualifications
|
|
92
|
+
- Technology, digital transformation, and society
|
|
93
|
+
|
|
94
|
+
Investigations will prioritize publicly accessible data, source attribution, transparent analysis, and reproducible results.
|
|
95
|
+
|
|
96
|
+
## Development Status
|
|
97
|
+
|
|
98
|
+
**Early development — project initialization**
|
|
99
|
+
|
|
100
|
+
The toolkit is currently being established. Its first development phase will focus on Python/Pandas data processing, dataset validation, and reproducible analysis workflows.
|
|
101
|
+
|
|
102
|
+
The project is not yet available as an installable Python package.
|
|
103
|
+
|
|
104
|
+
## Roadmap
|
|
105
|
+
|
|
106
|
+
- [ ] Establish Python package structure
|
|
107
|
+
- [ ] Implement CSV and JSON data loading
|
|
108
|
+
- [ ] Develop Pandas-based cleaning functions
|
|
109
|
+
- [ ] Add data validation and descriptive statistics
|
|
110
|
+
- [ ] Integrate API data collection
|
|
111
|
+
- [ ] Implement web scraping components
|
|
112
|
+
- [ ] Add visualization utilities
|
|
113
|
+
- [ ] Create a reproducible journalism case study
|
|
114
|
+
- [ ] Add automated tests and documentation
|
|
115
|
+
- [ ] Publish an initial release on PyPI
|
|
116
|
+
|
|
117
|
+
## Responsible Data Collection
|
|
118
|
+
|
|
119
|
+
Data collection functionality will be developed with attention to applicable laws, data protection, website access policies, rate limits, and source attribution.
|
|
120
|
+
|
|
121
|
+
## Author
|
|
122
|
+
|
|
123
|
+
**Irena Popova**
|
|
124
|
+
|
|
125
|
+
Independent Researcher | Python & Full-Stack Developer | Author & Publisher
|
|
126
|
+
|
|
127
|
+
[Website](https://www.irena-popova.com) | [GitHub](https://github.com/irenapopova)
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
An open-source license will be added before the first public software release.
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# TANGRA DataForge
|
|
2
|
+
|
|
3
|
+
**Open-Source Python Toolkit for Data Engineering, Analysis & Investigative Journalism**
|
|
4
|
+
|
|
5
|
+
TANGRA DataForge is an open-source Python project focused on collecting, processing, analyzing, and visualizing data. It is designed for data journalism, investigative research, and reproducible data analysis.
|
|
6
|
+
|
|
7
|
+
The project aims to transform raw data from public datasets, websites, and APIs into structured, verifiable, and meaningful information.
|
|
8
|
+
|
|
9
|
+
## Project Goals
|
|
10
|
+
|
|
11
|
+
- Collect data from public APIs, websites, CSV, JSON, and Excel files.
|
|
12
|
+
- Extract structured information through web scraping.
|
|
13
|
+
- Clean, validate, and transform datasets using Python and Pandas.
|
|
14
|
+
- Perform statistical analysis and identify patterns, trends, and anomalies.
|
|
15
|
+
- Create informative visualizations and charts.
|
|
16
|
+
- Support reproducible investigations with transparent methodologies and documented sources.
|
|
17
|
+
|
|
18
|
+
## Technology Stack
|
|
19
|
+
|
|
20
|
+
- **Python** — Core programming language
|
|
21
|
+
- **Pandas** — Data manipulation and analysis
|
|
22
|
+
- **NumPy** — Numerical computing
|
|
23
|
+
- **Beautiful Soup** — HTML parsing and web scraping
|
|
24
|
+
- **Requests** — HTTP requests and API integration
|
|
25
|
+
- **Matplotlib / Plotly** — Data visualization
|
|
26
|
+
- **pytest** — Automated testing
|
|
27
|
+
- **Git / GitHub** — Version control and collaboration
|
|
28
|
+
|
|
29
|
+
These technologies represent the planned toolkit. Dependencies will be introduced as features are implemented.
|
|
30
|
+
|
|
31
|
+
## Planned Architecture
|
|
32
|
+
|
|
33
|
+
```text
|
|
34
|
+
tangra-dataforge/
|
|
35
|
+
├── src/
|
|
36
|
+
│ └── tangra_dataforge/
|
|
37
|
+
│ ├── __init__.py
|
|
38
|
+
│ ├── collect.py
|
|
39
|
+
│ ├── clean.py
|
|
40
|
+
│ ├── analyze.py
|
|
41
|
+
│ └── visualize.py
|
|
42
|
+
├── investigations/
|
|
43
|
+
├── examples/
|
|
44
|
+
├── tests/
|
|
45
|
+
├── pyproject.toml
|
|
46
|
+
├── README.md
|
|
47
|
+
└── .gitignore
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Data Journalism & Research
|
|
51
|
+
|
|
52
|
+
TANGRA DataForge is being developed to support data-driven investigations into economic, social, political, and technological developments.
|
|
53
|
+
|
|
54
|
+
Planned research applications include:
|
|
55
|
+
|
|
56
|
+
- Economic indicators and regional inequality
|
|
57
|
+
- Housing affordability and income development
|
|
58
|
+
- Labour markets and workforce shortages
|
|
59
|
+
- Education and professional qualifications
|
|
60
|
+
- Technology, digital transformation, and society
|
|
61
|
+
|
|
62
|
+
Investigations will prioritize publicly accessible data, source attribution, transparent analysis, and reproducible results.
|
|
63
|
+
|
|
64
|
+
## Development Status
|
|
65
|
+
|
|
66
|
+
**Early development — project initialization**
|
|
67
|
+
|
|
68
|
+
The toolkit is currently being established. Its first development phase will focus on Python/Pandas data processing, dataset validation, and reproducible analysis workflows.
|
|
69
|
+
|
|
70
|
+
The project is not yet available as an installable Python package.
|
|
71
|
+
|
|
72
|
+
## Roadmap
|
|
73
|
+
|
|
74
|
+
- [ ] Establish Python package structure
|
|
75
|
+
- [ ] Implement CSV and JSON data loading
|
|
76
|
+
- [ ] Develop Pandas-based cleaning functions
|
|
77
|
+
- [ ] Add data validation and descriptive statistics
|
|
78
|
+
- [ ] Integrate API data collection
|
|
79
|
+
- [ ] Implement web scraping components
|
|
80
|
+
- [ ] Add visualization utilities
|
|
81
|
+
- [ ] Create a reproducible journalism case study
|
|
82
|
+
- [ ] Add automated tests and documentation
|
|
83
|
+
- [ ] Publish an initial release on PyPI
|
|
84
|
+
|
|
85
|
+
## Responsible Data Collection
|
|
86
|
+
|
|
87
|
+
Data collection functionality will be developed with attention to applicable laws, data protection, website access policies, rate limits, and source attribution.
|
|
88
|
+
|
|
89
|
+
## Author
|
|
90
|
+
|
|
91
|
+
**Irena Popova**
|
|
92
|
+
|
|
93
|
+
Independent Researcher | Python & Full-Stack Developer | Author & Publisher
|
|
94
|
+
|
|
95
|
+
[Website](https://www.irena-popova.com) | [GitHub](https://github.com/irenapopova)
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
An open-source license will be added before the first public software release.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
[build-system]
|
|
4
|
+
requires = ["hatchling>=1.26"]
|
|
5
|
+
build-backend = "hatchling.build"
|
|
6
|
+
|
|
7
|
+
[project]
|
|
8
|
+
name = "tangra-dataforge"
|
|
9
|
+
version = "0.1.0a1"
|
|
10
|
+
description = "Python toolkit for data collection, cleaning, analysis, and investigative journalism."
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
requires-python = ">=3.11"
|
|
13
|
+
license = "MIT"
|
|
14
|
+
authors = [
|
|
15
|
+
{ name = "Irena Popova" }
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
dependencies = [
|
|
19
|
+
"pandas>=2.0",
|
|
20
|
+
"requests>=2.31",
|
|
21
|
+
"openpyxl>=3.1",
|
|
22
|
+
"matplotlib>=3.8"
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
scraping = [
|
|
27
|
+
"scrapy>=2.12",
|
|
28
|
+
"beautifulsoup4>=4.12",
|
|
29
|
+
"lxml>=5.0",
|
|
30
|
+
"httpx>=0.27",
|
|
31
|
+
"playwright>=1.50"
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
analytics = [
|
|
35
|
+
"scipy>=1.13",
|
|
36
|
+
"duckdb>=1.1",
|
|
37
|
+
"pyarrow>=17.0"
|
|
38
|
+
]
|
|
39
|
+
|
|
40
|
+
visualization = [
|
|
41
|
+
"plotly>=5.24",
|
|
42
|
+
"geopandas>=1.0"
|
|
43
|
+
]
|
|
44
|
+
|
|
45
|
+
dev = [
|
|
46
|
+
"pytest>=8.0",
|
|
47
|
+
"build>=1.0",
|
|
48
|
+
"ruff>=0.9",
|
|
49
|
+
"mypy>=1.14"
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
[tool.pytest.ini_options]
|
|
53
|
+
testpaths = ["tests"]
|
|
54
|
+
|
|
55
|
+
[tool.hatch.build.targets.wheel]
|
|
56
|
+
packages = ["src/tangra_dataforge"]
|
|
File without changes
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
|
|
2
|
+
"""Statistical analysis utilities for TANGRA DataForge."""
|
|
3
|
+
|
|
4
|
+
import pandas as pd
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def describe_data(df: pd.DataFrame) -> pd.DataFrame:
|
|
8
|
+
"""Calculate descriptive statistics for numeric columns."""
|
|
9
|
+
return df.describe()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def calculate_percentage_change(
|
|
13
|
+
df: pd.DataFrame,
|
|
14
|
+
column: str,
|
|
15
|
+
periods: int = 1,
|
|
16
|
+
) -> pd.Series:
|
|
17
|
+
"""Calculate percentage change between observations."""
|
|
18
|
+
if column not in df.columns:
|
|
19
|
+
raise KeyError(f"Column not found: {column}")
|
|
20
|
+
|
|
21
|
+
if periods < 1:
|
|
22
|
+
raise ValueError("periods must be at least 1")
|
|
23
|
+
|
|
24
|
+
return df[column].pct_change(periods=periods) * 100
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def calculate_growth_rate(
|
|
28
|
+
initial_value: float,
|
|
29
|
+
final_value: float,
|
|
30
|
+
) -> float:
|
|
31
|
+
"""Calculate growth between two values as a percentage."""
|
|
32
|
+
if initial_value == 0:
|
|
33
|
+
raise ValueError("Initial value cannot be zero.")
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
(final_value - initial_value)
|
|
37
|
+
/ abs(initial_value)
|
|
38
|
+
) * 100
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def compare_columns(
|
|
42
|
+
df: pd.DataFrame,
|
|
43
|
+
column_a: str,
|
|
44
|
+
column_b: str,
|
|
45
|
+
) -> pd.DataFrame:
|
|
46
|
+
"""Compare two numeric columns using absolute differences."""
|
|
47
|
+
if column_a not in df.columns or column_b not in df.columns:
|
|
48
|
+
raise KeyError("One or both columns were not found.")
|
|
49
|
+
|
|
50
|
+
result = df[[column_a, column_b]].copy()
|
|
51
|
+
result["difference"] = result[column_a] - result[column_b]
|
|
52
|
+
|
|
53
|
+
return result
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def detect_outliers(
|
|
57
|
+
df: pd.DataFrame,
|
|
58
|
+
column: str,
|
|
59
|
+
threshold: float = 1.5,
|
|
60
|
+
) -> pd.DataFrame:
|
|
61
|
+
"""Detect outliers using the interquartile range (IQR)."""
|
|
62
|
+
if column not in df.columns:
|
|
63
|
+
raise KeyError(f"Column not found: {column}")
|
|
64
|
+
|
|
65
|
+
if threshold <= 0:
|
|
66
|
+
raise ValueError("threshold must be positive")
|
|
67
|
+
|
|
68
|
+
values = df[column].dropna()
|
|
69
|
+
|
|
70
|
+
if values.empty:
|
|
71
|
+
return df.iloc[0:0].copy()
|
|
72
|
+
|
|
73
|
+
q1 = values.quantile(0.25)
|
|
74
|
+
q3 = values.quantile(0.75)
|
|
75
|
+
iqr = q3 - q1
|
|
76
|
+
|
|
77
|
+
lower_bound = q1 - threshold * iqr
|
|
78
|
+
upper_bound = q3 + threshold * iqr
|
|
79
|
+
|
|
80
|
+
return df[
|
|
81
|
+
(df[column] < lower_bound)
|
|
82
|
+
| (df[column] > upper_bound)
|
|
83
|
+
].copy()
|
|
File without changes
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""Data cleaning utilities for TANGRA DataForge."""
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def remove_duplicates(df: pd.DataFrame) -> pd.DataFrame:
|
|
7
|
+
"""Remove duplicate rows and reset the index."""
|
|
8
|
+
return df.drop_duplicates().reset_index(drop=True)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def remove_empty_rows(df: pd.DataFrame) -> pd.DataFrame:
|
|
12
|
+
"""Remove rows where all values are missing."""
|
|
13
|
+
return df.dropna(how="all").reset_index(drop=True)
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Data collection utilities for TANGRA DataForge."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def load_csv(
|
|
10
|
+
path: str | Path,
|
|
11
|
+
**kwargs: Any,
|
|
12
|
+
) -> pd.DataFrame:
|
|
13
|
+
"""Load a CSV file into a Pandas DataFrame."""
|
|
14
|
+
return pd.read_csv(path, **kwargs)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def load_json(
|
|
18
|
+
path: str | Path,
|
|
19
|
+
**kwargs: Any,
|
|
20
|
+
) -> pd.DataFrame:
|
|
21
|
+
"""Load a JSON file into a Pandas DataFrame."""
|
|
22
|
+
return pd.read_json(path, **kwargs)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def load_excel(
|
|
26
|
+
path: str | Path,
|
|
27
|
+
sheet_name: str | int = 0,
|
|
28
|
+
**kwargs: Any,
|
|
29
|
+
) -> pd.DataFrame:
|
|
30
|
+
"""Load one Excel worksheet into a DataFrame."""
|
|
31
|
+
return pd.read_excel(
|
|
32
|
+
path,
|
|
33
|
+
sheet_name=sheet_name,
|
|
34
|
+
**kwargs,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def fetch_api(
|
|
39
|
+
url: str,
|
|
40
|
+
*,
|
|
41
|
+
timeout: float = 15.0,
|
|
42
|
+
params: dict[str, Any] | None = None,
|
|
43
|
+
) -> pd.DataFrame:
|
|
44
|
+
"""Fetch a JSON array of records from a public API.
|
|
45
|
+
|
|
46
|
+
The endpoint must return a JSON list of objects.
|
|
47
|
+
"""
|
|
48
|
+
import requests
|
|
49
|
+
|
|
50
|
+
response = requests.get(
|
|
51
|
+
url,
|
|
52
|
+
params=params,
|
|
53
|
+
timeout=timeout,
|
|
54
|
+
)
|
|
55
|
+
response.raise_for_status()
|
|
56
|
+
|
|
57
|
+
data = response.json()
|
|
58
|
+
|
|
59
|
+
if not isinstance(data, list) or not all(
|
|
60
|
+
isinstance(item, dict) for item in data
|
|
61
|
+
):
|
|
62
|
+
raise ValueError(
|
|
63
|
+
"API response must be a JSON list of objects."
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return pd.DataFrame(data)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
"""DuckDB integration for TANGRA DataForge.
|
|
4
|
+
|
|
5
|
+
Query Pandas DataFrames, CSV files, and Parquet datasets
|
|
6
|
+
using SQL.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
import duckdb
|
|
12
|
+
import pandas as pd
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def query_dataframe(
|
|
16
|
+
df: pd.DataFrame,
|
|
17
|
+
sql: str,
|
|
18
|
+
) -> pd.DataFrame:
|
|
19
|
+
"""Query a DataFrame registered as 'data'."""
|
|
20
|
+
with duckdb.connect(database=":memory:") as con:
|
|
21
|
+
con.register("data", df)
|
|
22
|
+
return con.execute(sql).fetchdf()
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def query_csv(
|
|
26
|
+
path: str | Path,
|
|
27
|
+
sql: str,
|
|
28
|
+
) -> pd.DataFrame:
|
|
29
|
+
"""Query a CSV file registered as 'data'."""
|
|
30
|
+
path = Path(path)
|
|
31
|
+
|
|
32
|
+
with duckdb.connect(database=":memory:") as con:
|
|
33
|
+
data = con.read_csv(str(path))
|
|
34
|
+
con.register("data", data)
|
|
35
|
+
return con.execute(sql).fetchdf()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def query_parquet(
|
|
39
|
+
path: str | Path,
|
|
40
|
+
sql: str,
|
|
41
|
+
) -> pd.DataFrame:
|
|
42
|
+
"""Query a Parquet file registered as 'data'."""
|
|
43
|
+
path = Path(path)
|
|
44
|
+
|
|
45
|
+
with duckdb.connect(database=":memory:") as con:
|
|
46
|
+
data = con.read_parquet(str(path))
|
|
47
|
+
con.register("data", data)
|
|
48
|
+
return con.execute(sql).fetchdf()
|
|
File without changes
|