qualityclean 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.
- qualityclean/__init__.py +15 -0
- qualityclean/__version__.py +1 -0
- qualityclean/core/__init__.py +0 -0
- qualityclean/core/cleaner.py +23 -0
- qualityclean/core/pipeline.py +45 -0
- qualityclean/io/__init__.py +1 -0
- qualityclean/io/exporter.py +24 -0
- qualityclean/io/loader.py +29 -0
- qualityclean/report/__init__.py +0 -0
- qualityclean/report/audit.py +55 -0
- qualityclean/report/builder.py +112 -0
- qualityclean/report/html.py +36 -0
- qualityclean/report/json.py +22 -0
- qualityclean/report/markdown.py +135 -0
- qualityclean/report/model.py +40 -0
- qualityclean/report/printer.py +231 -0
- qualityclean/report/templates/report.html +266 -0
- qualityclean/result.py +11 -0
- qualityclean/rules/__init__.py +0 -0
- qualityclean/rules/base.py +8 -0
- qualityclean/rules/column_rule.py +74 -0
- qualityclean/rules/datatype_rule.py +94 -0
- qualityclean/rules/duplicate_rule.py +28 -0
- qualityclean/rules/empty_rule.py +67 -0
- qualityclean/rules/missing_rule.py +171 -0
- qualityclean/rules/whitespace_rule.py +38 -0
- qualityclean-0.1.0.dist-info/METADATA +26 -0
- qualityclean-0.1.0.dist-info/RECORD +29 -0
- qualityclean-0.1.0.dist-info/WHEEL +4 -0
qualityclean/__init__.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from .__version__ import __version__
|
|
2
|
+
from .io.loader import load
|
|
3
|
+
from .io.exporter import export
|
|
4
|
+
from .core.cleaner import clean
|
|
5
|
+
from .report.audit import audit
|
|
6
|
+
from .result import CleanResult
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"__version__",
|
|
10
|
+
"clean",
|
|
11
|
+
"load",
|
|
12
|
+
"export",
|
|
13
|
+
"audit",
|
|
14
|
+
"CleanResult",
|
|
15
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
File without changes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import polars as pl
|
|
2
|
+
from qualityclean.core.pipeline import pipeline
|
|
3
|
+
from qualityclean.report.builder import ReportBuilder
|
|
4
|
+
from qualityclean.result import CleanResult
|
|
5
|
+
def clean(
|
|
6
|
+
df:pl.DataFrame,
|
|
7
|
+
**kwargs,
|
|
8
|
+
) -> CleanResult:
|
|
9
|
+
builder = ReportBuilder()
|
|
10
|
+
builder.start(
|
|
11
|
+
df,
|
|
12
|
+
fill_mode=kwargs.get("fill",False)
|
|
13
|
+
)
|
|
14
|
+
df = pipeline(
|
|
15
|
+
df,
|
|
16
|
+
builder=builder,
|
|
17
|
+
**kwargs,
|
|
18
|
+
)
|
|
19
|
+
builder.finish(df)
|
|
20
|
+
return CleanResult(
|
|
21
|
+
df = df,
|
|
22
|
+
report=builder.build()
|
|
23
|
+
)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from time import perf_counter
|
|
2
|
+
|
|
3
|
+
import polars as pl
|
|
4
|
+
|
|
5
|
+
from qualityclean.rules.column_rule import ColumnRule
|
|
6
|
+
from qualityclean.rules.datatype_rule import DatatypeRule
|
|
7
|
+
from qualityclean.rules.duplicate_rule import DuplicateRule
|
|
8
|
+
from qualityclean.rules.empty_rule import EmptyRule
|
|
9
|
+
from qualityclean.rules.missing_rule import MissingRule
|
|
10
|
+
from qualityclean.rules.whitespace_rule import WhitespaceRule
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
DEFAULT_PIPELINE = (
|
|
14
|
+
ColumnRule(),
|
|
15
|
+
WhitespaceRule(),
|
|
16
|
+
EmptyRule(),
|
|
17
|
+
DatatypeRule(),
|
|
18
|
+
MissingRule(),
|
|
19
|
+
DuplicateRule(),
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def pipeline(
|
|
24
|
+
df: pl.DataFrame,
|
|
25
|
+
**kwargs,
|
|
26
|
+
) -> pl.DataFrame:
|
|
27
|
+
|
|
28
|
+
builder = kwargs.get("builder")
|
|
29
|
+
|
|
30
|
+
for rule in DEFAULT_PIPELINE:
|
|
31
|
+
|
|
32
|
+
start = perf_counter()
|
|
33
|
+
|
|
34
|
+
df = rule.run(
|
|
35
|
+
df,
|
|
36
|
+
**kwargs,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
if builder is not None:
|
|
40
|
+
builder.record_rule_time(
|
|
41
|
+
rule.__class__.__name__,
|
|
42
|
+
perf_counter() - start,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
return df
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import polars as pl
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from qualityclean.result import CleanResult
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def export(data:pl.DataFrame,path:str|Path)-> None:
|
|
7
|
+
"""Export a Polars DataFrame to support file format."""
|
|
8
|
+
|
|
9
|
+
if isinstance(data,CleanResult):
|
|
10
|
+
data = data.df
|
|
11
|
+
|
|
12
|
+
path = Path(path)
|
|
13
|
+
ext = path.suffix.lower()
|
|
14
|
+
|
|
15
|
+
if ext == ".csv":
|
|
16
|
+
data.write_csv(path)
|
|
17
|
+
return
|
|
18
|
+
if ext == ".parquet":
|
|
19
|
+
data.write_parquet(path)
|
|
20
|
+
return
|
|
21
|
+
|
|
22
|
+
raise ValueError(
|
|
23
|
+
f"Unsupported file extension: {ext}. Expected '.csv' or '.parquet'"
|
|
24
|
+
)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import polars as pl
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def load(data: str | Path | pl.DataFrame) -> pl.DataFrame:
|
|
6
|
+
"""Load a CSV, Parquet file, or Polars DataFrame."""
|
|
7
|
+
|
|
8
|
+
if isinstance(data, pl.DataFrame):
|
|
9
|
+
return data
|
|
10
|
+
|
|
11
|
+
path = Path(data)
|
|
12
|
+
|
|
13
|
+
if not path.exists():
|
|
14
|
+
raise FileNotFoundError(f"File not found: '{path}'")
|
|
15
|
+
|
|
16
|
+
if not path.is_file():
|
|
17
|
+
raise IsADirectoryError(f"'{path}' is a directory, expected a file.")
|
|
18
|
+
|
|
19
|
+
ext = path.suffix.lower()
|
|
20
|
+
|
|
21
|
+
if ext == ".csv":
|
|
22
|
+
return pl.read_csv(path)
|
|
23
|
+
|
|
24
|
+
if ext == ".parquet":
|
|
25
|
+
return pl.read_parquet(path)
|
|
26
|
+
|
|
27
|
+
raise ValueError(
|
|
28
|
+
f"Unsupported file extension: '{ext}'. Expected '.csv' or '.parquet'."
|
|
29
|
+
)
|
|
File without changes
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
from qualityclean.result import CleanResult
|
|
2
|
+
|
|
3
|
+
from .printer import print_report
|
|
4
|
+
from .html import export_html
|
|
5
|
+
from .markdown import export_markdown
|
|
6
|
+
from .json import export_json
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def audit(
|
|
10
|
+
result: CleanResult,
|
|
11
|
+
format: str = "print",
|
|
12
|
+
path: str | None = None,
|
|
13
|
+
) -> None:
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
if not isinstance(result, CleanResult):
|
|
17
|
+
raise TypeError(
|
|
18
|
+
"qc.audit() expects a CleanResult returned by qc.clean().\n\n"
|
|
19
|
+
"Example:\n"
|
|
20
|
+
" result = qc.clean(df)\n"
|
|
21
|
+
" qc.audit(result)"
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
report = result.report
|
|
25
|
+
|
|
26
|
+
format = format.lower()
|
|
27
|
+
|
|
28
|
+
if format == "print":
|
|
29
|
+
print_report(result.report)
|
|
30
|
+
|
|
31
|
+
elif format == "html":
|
|
32
|
+
if path is None:
|
|
33
|
+
raise ValueError(
|
|
34
|
+
"'path' is required when exporting an HTML report."
|
|
35
|
+
)
|
|
36
|
+
export_html(result.report, path)
|
|
37
|
+
|
|
38
|
+
elif format == "markdown":
|
|
39
|
+
if path is None:
|
|
40
|
+
raise ValueError(
|
|
41
|
+
"'path' is required when exporting a Markdown report."
|
|
42
|
+
)
|
|
43
|
+
export_markdown(result.report, path)
|
|
44
|
+
|
|
45
|
+
elif format == "json":
|
|
46
|
+
if path is None:
|
|
47
|
+
raise ValueError(
|
|
48
|
+
"'path' is required when exporting a JSON report."
|
|
49
|
+
)
|
|
50
|
+
export_json(result.report, path)
|
|
51
|
+
|
|
52
|
+
else:
|
|
53
|
+
raise ValueError(
|
|
54
|
+
"format must be one of: print, html, markdown, json"
|
|
55
|
+
)
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import time
|
|
2
|
+
import platform
|
|
3
|
+
import sys
|
|
4
|
+
import polars as pl
|
|
5
|
+
|
|
6
|
+
from qualityclean.report.model import Report
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ReportBuilder:
|
|
10
|
+
def __init__(self):
|
|
11
|
+
self.report = Report()
|
|
12
|
+
self._start_time = 0.0
|
|
13
|
+
|
|
14
|
+
def start(
|
|
15
|
+
self,
|
|
16
|
+
df: pl.DataFrame,
|
|
17
|
+
*,
|
|
18
|
+
fill_mode: bool = False,
|
|
19
|
+
) -> None:
|
|
20
|
+
"""Capture the initial state of the dataset before cleaning."""
|
|
21
|
+
|
|
22
|
+
self.report.fill_mode = fill_mode
|
|
23
|
+
self.report.original_rows = df.height
|
|
24
|
+
self.report.original_columns = df.width
|
|
25
|
+
self.report.memory_before = df.estimated_size("mb")
|
|
26
|
+
self.report.python_version = (
|
|
27
|
+
f"{sys.version_info.major}."
|
|
28
|
+
f"{sys.version_info.minor}."
|
|
29
|
+
f"{sys.version_info.micro}"
|
|
30
|
+
)
|
|
31
|
+
self.report.polars_version = pl.__version__
|
|
32
|
+
self.report.platform = platform.system()
|
|
33
|
+
|
|
34
|
+
self.report.original_schema = {
|
|
35
|
+
column: str(dtype)
|
|
36
|
+
for column, dtype in df.schema.items()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
self._start_time = time.perf_counter()
|
|
40
|
+
|
|
41
|
+
def finish(
|
|
42
|
+
self,
|
|
43
|
+
df: pl.DataFrame,
|
|
44
|
+
) -> None:
|
|
45
|
+
"""Capture the final state of the dataset after cleaning."""
|
|
46
|
+
|
|
47
|
+
self.report.final_rows = df.height
|
|
48
|
+
self.report.final_columns = df.width
|
|
49
|
+
self.report.memory_after = df.estimated_size("mb")
|
|
50
|
+
|
|
51
|
+
self.report.final_schema = {
|
|
52
|
+
column: str(dtype)
|
|
53
|
+
for column, dtype in df.schema.items()
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
self.report.execution_time = (
|
|
57
|
+
time.perf_counter() - self._start_time
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def build(self) -> Report:
|
|
61
|
+
"""Return the completed report."""
|
|
62
|
+
|
|
63
|
+
return self.report
|
|
64
|
+
|
|
65
|
+
def record_rule_time(
|
|
66
|
+
self,
|
|
67
|
+
rule: str,
|
|
68
|
+
elapsed: float,
|
|
69
|
+
) -> None:
|
|
70
|
+
"""Record execution time for a cleaning rule."""
|
|
71
|
+
|
|
72
|
+
self.report.rule_timings[rule] = elapsed
|
|
73
|
+
|
|
74
|
+
def record_whitespace_fixed(
|
|
75
|
+
self,
|
|
76
|
+
count: int,
|
|
77
|
+
) -> None:
|
|
78
|
+
"""Record the number of whitespace fixes."""
|
|
79
|
+
|
|
80
|
+
self.report.whitespace_fixed += count
|
|
81
|
+
|
|
82
|
+
def record_placeholders_converted(
|
|
83
|
+
self,
|
|
84
|
+
count: int,
|
|
85
|
+
) -> None:
|
|
86
|
+
"""Record the number of placeholders converted."""
|
|
87
|
+
|
|
88
|
+
self.report.placeholders_converted += count
|
|
89
|
+
|
|
90
|
+
def record_missing_filled(
|
|
91
|
+
self,
|
|
92
|
+
count: int,
|
|
93
|
+
) -> None:
|
|
94
|
+
"""Record the number of missing values filled."""
|
|
95
|
+
|
|
96
|
+
self.report.missing_filled += count
|
|
97
|
+
|
|
98
|
+
def record_missing_dropped(
|
|
99
|
+
self,
|
|
100
|
+
count: int,
|
|
101
|
+
) -> None:
|
|
102
|
+
"""Record the number of missing values dropped."""
|
|
103
|
+
|
|
104
|
+
self.report.missing_dropped += count
|
|
105
|
+
|
|
106
|
+
def record_duplicates_removed(
|
|
107
|
+
self,
|
|
108
|
+
count: int,
|
|
109
|
+
) -> None:
|
|
110
|
+
"""Record the number of duplicates removed."""
|
|
111
|
+
|
|
112
|
+
self.report.duplicates_removed += count
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from jinja2 import Environment, PackageLoader, select_autoescape
|
|
4
|
+
|
|
5
|
+
from qualityclean.report.model import Report
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
env = Environment(
|
|
9
|
+
loader=PackageLoader(
|
|
10
|
+
"qualityclean.report",
|
|
11
|
+
"templates",
|
|
12
|
+
),
|
|
13
|
+
autoescape=select_autoescape(["html"]),
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def render_html(report: Report) -> str:
|
|
18
|
+
"""Render a QualityClean report as HTML."""
|
|
19
|
+
|
|
20
|
+
template = env.get_template("report.html")
|
|
21
|
+
|
|
22
|
+
return template.render(report=report)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def export_html(
|
|
26
|
+
report: Report,
|
|
27
|
+
path: str | Path,
|
|
28
|
+
) -> None:
|
|
29
|
+
"""Export a QualityClean report as an HTML file."""
|
|
30
|
+
|
|
31
|
+
path = Path(path)
|
|
32
|
+
|
|
33
|
+
path.write_text(
|
|
34
|
+
render_html(report),
|
|
35
|
+
encoding="utf-8",
|
|
36
|
+
)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from dataclasses import asdict
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from qualityclean.report.model import Report
|
|
6
|
+
|
|
7
|
+
def export_json(
|
|
8
|
+
report: Report,
|
|
9
|
+
path:str | Path,
|
|
10
|
+
)-> None :
|
|
11
|
+
"""Export a QualityClean report as JSON FILE."""
|
|
12
|
+
path = Path(path)
|
|
13
|
+
with path.open(
|
|
14
|
+
mode="w",
|
|
15
|
+
encoding="utf-8",
|
|
16
|
+
) as file:
|
|
17
|
+
json.dump(
|
|
18
|
+
asdict(report),
|
|
19
|
+
file,
|
|
20
|
+
indent=4,
|
|
21
|
+
ensure_ascii=False
|
|
22
|
+
)
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from qualityclean.report.model import Report
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def render_markdown(
|
|
7
|
+
report: Report,
|
|
8
|
+
) -> str:
|
|
9
|
+
"""Render a QualityClean report as Markdown."""
|
|
10
|
+
|
|
11
|
+
lines: list[str] = []
|
|
12
|
+
|
|
13
|
+
lines.append(f"# QualityClean Report v{report.version}")
|
|
14
|
+
lines.append("")
|
|
15
|
+
|
|
16
|
+
# ==========================
|
|
17
|
+
# Dataset Summary
|
|
18
|
+
# ==========================
|
|
19
|
+
|
|
20
|
+
lines.append("## Dataset Summary")
|
|
21
|
+
lines.append("")
|
|
22
|
+
lines.append("| Metric | Value |")
|
|
23
|
+
lines.append("|-------|------:|")
|
|
24
|
+
lines.append(f"| Original Rows | {report.original_rows} |")
|
|
25
|
+
lines.append(f"| Final Rows | {report.final_rows} |")
|
|
26
|
+
lines.append(f"| Original Columns | {report.original_columns} |")
|
|
27
|
+
lines.append(f"| Final Columns | {report.final_columns} |")
|
|
28
|
+
lines.append("")
|
|
29
|
+
|
|
30
|
+
# ==========================
|
|
31
|
+
# Cleaning Summary
|
|
32
|
+
# ==========================
|
|
33
|
+
|
|
34
|
+
lines.append("## Cleaning Summary")
|
|
35
|
+
lines.append("")
|
|
36
|
+
lines.append("| Operation | Count |")
|
|
37
|
+
lines.append("|-----------|------:|")
|
|
38
|
+
lines.append(
|
|
39
|
+
f"| Whitespace Fixed | {report.whitespace_fixed} |"
|
|
40
|
+
)
|
|
41
|
+
lines.append(
|
|
42
|
+
f"| Placeholders Converted | {report.placeholders_converted} |"
|
|
43
|
+
)
|
|
44
|
+
lines.append(
|
|
45
|
+
f"| Missing Values Filled | {report.missing_filled} |"
|
|
46
|
+
)
|
|
47
|
+
lines.append(
|
|
48
|
+
f"| Missing Values Dropped | {report.missing_dropped} |"
|
|
49
|
+
)
|
|
50
|
+
lines.append(
|
|
51
|
+
f"| Duplicates Removed | {report.duplicates_removed} |"
|
|
52
|
+
)
|
|
53
|
+
lines.append(
|
|
54
|
+
f"| Datatypes Changed | {report.datatypes_changed} |"
|
|
55
|
+
)
|
|
56
|
+
lines.append("")
|
|
57
|
+
|
|
58
|
+
# ==========================
|
|
59
|
+
# Datatype Changes
|
|
60
|
+
# ==========================
|
|
61
|
+
|
|
62
|
+
if report.datatype_changes:
|
|
63
|
+
|
|
64
|
+
lines.append("## Datatype Changes")
|
|
65
|
+
lines.append("")
|
|
66
|
+
lines.append("| Column | Before | After |")
|
|
67
|
+
lines.append("|--------|--------|-------|")
|
|
68
|
+
|
|
69
|
+
for column, (before, after) in report.datatype_changes.items():
|
|
70
|
+
lines.append(
|
|
71
|
+
f"| {column} | {before} | {after} |"
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
lines.append("")
|
|
75
|
+
|
|
76
|
+
# ==========================
|
|
77
|
+
# Original Schema
|
|
78
|
+
# ==========================
|
|
79
|
+
|
|
80
|
+
if report.original_schema:
|
|
81
|
+
|
|
82
|
+
lines.append("## Original Schema")
|
|
83
|
+
lines.append("")
|
|
84
|
+
lines.append("| Column | Datatype |")
|
|
85
|
+
lines.append("|--------|----------|")
|
|
86
|
+
|
|
87
|
+
for column, dtype in report.original_schema.items():
|
|
88
|
+
lines.append(f"| {column} | {dtype} |")
|
|
89
|
+
|
|
90
|
+
lines.append("")
|
|
91
|
+
|
|
92
|
+
# ==========================
|
|
93
|
+
# Final Schema
|
|
94
|
+
# ==========================
|
|
95
|
+
|
|
96
|
+
if report.final_schema:
|
|
97
|
+
|
|
98
|
+
lines.append("## Final Schema")
|
|
99
|
+
lines.append("")
|
|
100
|
+
lines.append("| Column | Datatype |")
|
|
101
|
+
lines.append("|--------|----------|")
|
|
102
|
+
|
|
103
|
+
for column, dtype in report.final_schema.items():
|
|
104
|
+
lines.append(f"| {column} | {dtype} |")
|
|
105
|
+
|
|
106
|
+
lines.append("")
|
|
107
|
+
|
|
108
|
+
# ==========================
|
|
109
|
+
# Execution
|
|
110
|
+
# ==========================
|
|
111
|
+
|
|
112
|
+
lines.append("## Execution")
|
|
113
|
+
lines.append("")
|
|
114
|
+
lines.append("| Metric | Value |")
|
|
115
|
+
lines.append("|--------|------:|")
|
|
116
|
+
lines.append(f"| Fill Mode | {report.fill_mode} |")
|
|
117
|
+
lines.append(
|
|
118
|
+
f"| Execution Time | {report.execution_time:.4f} seconds |"
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
return "\n".join(lines)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def export_markdown(
|
|
125
|
+
report: Report,
|
|
126
|
+
path: str | Path,
|
|
127
|
+
) -> None:
|
|
128
|
+
"""Export a QualityClean report as a Markdown file."""
|
|
129
|
+
|
|
130
|
+
path = Path(path)
|
|
131
|
+
|
|
132
|
+
path.write_text(
|
|
133
|
+
render_markdown(report),
|
|
134
|
+
encoding="utf-8",
|
|
135
|
+
)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
@dataclass(slots=True)
|
|
5
|
+
class Report:
|
|
6
|
+
|
|
7
|
+
version: str = "0.1.0"
|
|
8
|
+
|
|
9
|
+
fill_mode: bool = False
|
|
10
|
+
|
|
11
|
+
original_rows: int = 0
|
|
12
|
+
final_rows: int = 0
|
|
13
|
+
|
|
14
|
+
original_columns: int = 0
|
|
15
|
+
final_columns: int = 0
|
|
16
|
+
|
|
17
|
+
original_schema: dict[str, str] = field(default_factory=dict)
|
|
18
|
+
final_schema: dict[str, str] = field(default_factory=dict)
|
|
19
|
+
|
|
20
|
+
whitespace_fixed: int = 0
|
|
21
|
+
placeholders_converted: int = 0
|
|
22
|
+
|
|
23
|
+
missing_filled: int = 0
|
|
24
|
+
missing_dropped: int = 0
|
|
25
|
+
|
|
26
|
+
duplicates_removed: int = 0
|
|
27
|
+
|
|
28
|
+
datatypes_changed: int = 0
|
|
29
|
+
datatype_changes: dict[str, str] = field(default_factory=dict)
|
|
30
|
+
|
|
31
|
+
memory_before: float = 0.0
|
|
32
|
+
memory_after: float = 0.0
|
|
33
|
+
|
|
34
|
+
python_version: str = ""
|
|
35
|
+
polars_version: str = ""
|
|
36
|
+
platform: str = ""
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
rule_timings: dict[str, float] = field(default_factory=dict)
|
|
40
|
+
execution_time: float = 0.0
|