PyDPEET 0.2.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.
- pydpeet/__init__.py +56 -0
- pydpeet/citations/__init__.py +7 -0
- pydpeet/citations/citeme.py +397 -0
- pydpeet/citations/html_writer.py +137 -0
- pydpeet/dev_utils/__init__.py +7 -0
- pydpeet/dev_utils/generate_inits/__init__.py +7 -0
- pydpeet/dev_utils/generate_inits/generate_inits.py +327 -0
- pydpeet/io/__init__.py +7 -0
- pydpeet/io/configs/__init__.py +7 -0
- pydpeet/io/configs/config.py +227 -0
- pydpeet/io/configs/const.py +1 -0
- pydpeet/io/convert.py +594 -0
- pydpeet/io/device/__init__.py +7 -0
- pydpeet/io/device/arbin_4_23_PV090331/__init__.py +7 -0
- pydpeet/io/device/arbin_4_23_PV090331/formatter.py +22 -0
- pydpeet/io/device/arbin_4_23_PV090331/mapper.py +18 -0
- pydpeet/io/device/arbin_4_23_PV090331/reader.py +68 -0
- pydpeet/io/device/arbin_8_00_PV221201/__init__.py +7 -0
- pydpeet/io/device/arbin_8_00_PV221201/formatter.py +26 -0
- pydpeet/io/device/arbin_8_00_PV221201/mapper.py +18 -0
- pydpeet/io/device/arbin_8_00_PV221201/reader.py +66 -0
- pydpeet/io/device/basytec_6_3_1_0/__init__.py +7 -0
- pydpeet/io/device/basytec_6_3_1_0/formatter.py +18 -0
- pydpeet/io/device/basytec_6_3_1_0/mapper.py +18 -0
- pydpeet/io/device/basytec_6_3_1_0/reader.py +54 -0
- pydpeet/io/device/digatron_4_20_6_236/__init__.py +7 -0
- pydpeet/io/device/digatron_4_20_6_236/formatter.py +27 -0
- pydpeet/io/device/digatron_4_20_6_236/mapper.py +18 -0
- pydpeet/io/device/digatron_4_20_6_236/reader.py +45 -0
- pydpeet/io/device/digatron_eis_4_20_6_236/__init__.py +7 -0
- pydpeet/io/device/digatron_eis_4_20_6_236/formatter.py +39 -0
- pydpeet/io/device/digatron_eis_4_20_6_236/mapper.py +18 -0
- pydpeet/io/device/digatron_eis_4_20_6_236/reader.py +42 -0
- pydpeet/io/device/neware_8_0_0_516/__init__.py +7 -0
- pydpeet/io/device/neware_8_0_0_516/formatter.py +31 -0
- pydpeet/io/device/neware_8_0_0_516/mapper.py +18 -0
- pydpeet/io/device/neware_8_0_0_516/reader.py +397 -0
- pydpeet/io/device/parstat_2_63_3/__init__.py +7 -0
- pydpeet/io/device/parstat_2_63_3/formatter.py +34 -0
- pydpeet/io/device/parstat_2_63_3/mapper.py +18 -0
- pydpeet/io/device/parstat_2_63_3/reader.py +34 -0
- pydpeet/io/device/safion_1_9/__init__.py +7 -0
- pydpeet/io/device/safion_1_9/formatter.py +25 -0
- pydpeet/io/device/safion_1_9/mapper.py +18 -0
- pydpeet/io/device/safion_1_9/reader.py +89 -0
- pydpeet/io/device/zahner/__init__.py +7 -0
- pydpeet/io/device/zahner/formatter.py +73 -0
- pydpeet/io/device/zahner/mapper.py +34 -0
- pydpeet/io/device/zahner/reader.py +46 -0
- pydpeet/io/device/zahner_new/__init__.py +7 -0
- pydpeet/io/device/zahner_new/formatter.py +72 -0
- pydpeet/io/device/zahner_new/mapper.py +50 -0
- pydpeet/io/device/zahner_new/reader.py +46 -0
- pydpeet/io/map.py +69 -0
- pydpeet/io/read.py +50 -0
- pydpeet/io/utils/__init__.py +7 -0
- pydpeet/io/utils/ext_path.py +34 -0
- pydpeet/io/utils/formatter_utils.py +429 -0
- pydpeet/io/utils/load_custom_module.py +33 -0
- pydpeet/io/utils/timing.py +36 -0
- pydpeet/io/write.py +99 -0
- pydpeet/process/__init__.py +7 -0
- pydpeet/process/analyze/__init__.py +7 -0
- pydpeet/process/analyze/average.py +297 -0
- pydpeet/process/analyze/capacity.py +208 -0
- pydpeet/process/analyze/configs/__init__.py +7 -0
- pydpeet/process/analyze/configs/battery_config.py +38 -0
- pydpeet/process/analyze/configs/ocv_config.py +177 -0
- pydpeet/process/analyze/configs/step_analyzer_config.py +178 -0
- pydpeet/process/analyze/cycle.py +63 -0
- pydpeet/process/analyze/efficiency.py +182 -0
- pydpeet/process/analyze/energy.py +51 -0
- pydpeet/process/analyze/extract/__init__.py +7 -0
- pydpeet/process/analyze/extract/dva_ica.py +224 -0
- pydpeet/process/analyze/extract/ocv.py +196 -0
- pydpeet/process/analyze/power.py +32 -0
- pydpeet/process/analyze/resistance.py +87 -0
- pydpeet/process/analyze/soc.py +418 -0
- pydpeet/process/analyze/soh.py +72 -0
- pydpeet/process/analyze/utils.py +122 -0
- pydpeet/process/merge/__init__.py +7 -0
- pydpeet/process/merge/series.py +274 -0
- pydpeet/process/sequence/__init__.py +7 -0
- pydpeet/process/sequence/configs/__init__.py +7 -0
- pydpeet/process/sequence/configs/config.py +17 -0
- pydpeet/process/sequence/step_analyzer.py +440 -0
- pydpeet/process/sequence/utils/__init__.py +7 -0
- pydpeet/process/sequence/utils/annotate/__init__.py +7 -0
- pydpeet/process/sequence/utils/annotate/annotate_primitives.py +419 -0
- pydpeet/process/sequence/utils/configs/CONFIG_Fallback.py +231 -0
- pydpeet/process/sequence/utils/configs/CONFIG_preprocessing.py +243 -0
- pydpeet/process/sequence/utils/configs/__init__.py +7 -0
- pydpeet/process/sequence/utils/console_prints/__init__.py +7 -0
- pydpeet/process/sequence/utils/console_prints/log_time.py +30 -0
- pydpeet/process/sequence/utils/postprocessing/__init__.py +7 -0
- pydpeet/process/sequence/utils/postprocessing/df_primitives_correction.py +166 -0
- pydpeet/process/sequence/utils/postprocessing/filter_df.py +168 -0
- pydpeet/process/sequence/utils/postprocessing/generate_instructions.py +297 -0
- pydpeet/process/sequence/utils/preprocessing/__init__.py +7 -0
- pydpeet/process/sequence/utils/preprocessing/calculate_thresholds.py +27 -0
- pydpeet/process/sequence/utils/processing/__init__.py +7 -0
- pydpeet/process/sequence/utils/processing/analyze_segments.py +368 -0
- pydpeet/process/sequence/utils/processing/attempt_to_merge_neighboring_segments.py +122 -0
- pydpeet/process/sequence/utils/processing/check_CV_results.py +226 -0
- pydpeet/process/sequence/utils/processing/check_power_zero_watt_segments.py +85 -0
- pydpeet/process/sequence/utils/processing/check_zero_length.py +117 -0
- pydpeet/process/sequence/utils/processing/split_in_segments.py +97 -0
- pydpeet/process/sequence/utils/processing/supress_smaller_segments.py +99 -0
- pydpeet/process/sequence/utils/processing/widen_constant_segments.py +185 -0
- pydpeet/process/sequence/utils/visualize/__init__.py +7 -0
- pydpeet/process/sequence/utils/visualize/visualize_data.py +288 -0
- pydpeet/res/__init__.py +7 -0
- pydpeet/settings.py +0 -0
- pydpeet/utils/__init__.py +7 -0
- pydpeet/utils/logging_style.py +28 -0
- pydpeet/version.py +0 -0
- pydpeet-0.2.0.dist-info/METADATA +85 -0
- pydpeet-0.2.0.dist-info/RECORD +122 -0
- pydpeet-0.2.0.dist-info/WHEEL +5 -0
- pydpeet-0.2.0.dist-info/licenses/AUTHORS.md +0 -0
- pydpeet-0.2.0.dist-info/licenses/LICENCE.md +27 -0
- pydpeet-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
|
|
3
|
+
from pydpeet.io.utils.formatter_utils import nan_to_none_in_column, round_testtime, typecast
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_data_into_format_zahner_1(df: pd.DataFrame) -> pd.DataFrame:
|
|
7
|
+
"""
|
|
8
|
+
Format a DataFrame in the format of a Zahner EIS file, type 1.
|
|
9
|
+
|
|
10
|
+
The DataFrame is modified in-place.
|
|
11
|
+
|
|
12
|
+
The modifications are as follows:
|
|
13
|
+
|
|
14
|
+
- The "Test_Time[s]" column is rounded to 5 decimal places.
|
|
15
|
+
- NaN values in the "Test_Time[s]" column are replaced with None.
|
|
16
|
+
- The "Step_Count" column is typecast to int.
|
|
17
|
+
- The "EIS_f[Hz]" column is typecast to float.
|
|
18
|
+
- The "EIS_Z_Real[Ohm]" column is typecast to float.
|
|
19
|
+
- The "EIS_Z_Imag[Ohm]" column is typecast to float.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
dataFrame : pandas.DataFrame
|
|
24
|
+
DataFrame to be modified.
|
|
25
|
+
|
|
26
|
+
Returns
|
|
27
|
+
-------
|
|
28
|
+
pandas.DataFrame
|
|
29
|
+
Modified DataFrame.
|
|
30
|
+
"""
|
|
31
|
+
df = round_testtime(df)
|
|
32
|
+
df = nan_to_none_in_column(df, "Test_Time[s]")
|
|
33
|
+
typecast(df, "Step_Count", int)
|
|
34
|
+
typecast(df, "EIS_f[Hz]", float)
|
|
35
|
+
typecast(df, "EIS_Z_Real[Ohm]", float)
|
|
36
|
+
typecast(df, "EIS_Z_Imag[Ohm]", float)
|
|
37
|
+
|
|
38
|
+
return df
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_data_into_format_zahner_2(df: pd.DataFrame) -> pd.DataFrame:
|
|
42
|
+
"""
|
|
43
|
+
Format a DataFrame in the format of a Zahner EIS file, type 2.
|
|
44
|
+
|
|
45
|
+
The DataFrame is modified in-place.
|
|
46
|
+
|
|
47
|
+
The modifications are as follows:
|
|
48
|
+
|
|
49
|
+
- The "Test_Time[s]" column is rounded to 5 decimal places.
|
|
50
|
+
- NaN values in the "Test_Time[s]" column are replaced with None.
|
|
51
|
+
- The "Step_Count" column is typecast to int.
|
|
52
|
+
- The "Voltage[V]" column is typecast to float.
|
|
53
|
+
- The "Current[A]" column is typecast to float.
|
|
54
|
+
- The "Test_Time[s]" column is typecast to float.
|
|
55
|
+
|
|
56
|
+
Parameters
|
|
57
|
+
----------
|
|
58
|
+
dataFrame : pandas.DataFrame
|
|
59
|
+
DataFrame to be modified.
|
|
60
|
+
|
|
61
|
+
Returns
|
|
62
|
+
-------
|
|
63
|
+
pandas.DataFrame
|
|
64
|
+
Modified DataFrame.
|
|
65
|
+
"""
|
|
66
|
+
df = round_testtime(df)
|
|
67
|
+
df = nan_to_none_in_column(df, "Test_Time[s]")
|
|
68
|
+
typecast(df, "Step_Count", int)
|
|
69
|
+
typecast(df, "Voltage[V]", float)
|
|
70
|
+
typecast(df, "Current[A]", float)
|
|
71
|
+
typecast(df, "Test_Time[s]", float)
|
|
72
|
+
|
|
73
|
+
return df
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Map raw-data column names (left) to standardized column names (right)
|
|
2
|
+
COLUMN_MAP_1 = {
|
|
3
|
+
"Frequency/Hz": "EIS_f[Hz]",
|
|
4
|
+
"Number": "Step_Count",
|
|
5
|
+
r"R/": "EIS_Z_Real[Ohm]",
|
|
6
|
+
r"I/": "EIS_Z_Imag[Ohm]",
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
COLUMN_MAP_2 = {
|
|
10
|
+
"step": "Step_Count",
|
|
11
|
+
"voltage V": "Voltage[V]",
|
|
12
|
+
"time s": "Test_Time[s]",
|
|
13
|
+
"current A": "Current[A]",
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
# Default columns of the standardized format
|
|
17
|
+
# which are not present in the raw data files.
|
|
18
|
+
MISSING_REQUIRED_COLUMNS_1 = [
|
|
19
|
+
"Test_Time[s]",
|
|
20
|
+
"Voltage[V]",
|
|
21
|
+
"Current[A]",
|
|
22
|
+
"Date_Time",
|
|
23
|
+
"Temperature[°C]",
|
|
24
|
+
"EIS_DC[A]",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
MISSING_REQUIRED_COLUMNS_2 = [
|
|
28
|
+
"EIS_DC[A]",
|
|
29
|
+
"Temperature[°C]",
|
|
30
|
+
"Date_Time",
|
|
31
|
+
"EIS_f[Hz]",
|
|
32
|
+
"EIS_Z_Real[Ohm]",
|
|
33
|
+
"EIS_Z_Imag[Ohm]",
|
|
34
|
+
]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def to_dataframe(input_path: str) -> tuple[pd.DataFrame, str]:
|
|
5
|
+
"""
|
|
6
|
+
Parses the input file from the new Zahner Cycler into a pandas DataFrame.
|
|
7
|
+
|
|
8
|
+
Parameters:
|
|
9
|
+
input_path (str): Path to the input file.
|
|
10
|
+
|
|
11
|
+
Returns:
|
|
12
|
+
(pd.DataFrame, str): A tuple containing the DataFrame with data and metadata as a string.
|
|
13
|
+
"""
|
|
14
|
+
metadata = []
|
|
15
|
+
|
|
16
|
+
# Open the file and process lines
|
|
17
|
+
with open(input_path, encoding="us-ascii") as file:
|
|
18
|
+
# Read metadata until a data header line is detected
|
|
19
|
+
line = file.readline()
|
|
20
|
+
while not (line.strip().startswith("time") or line.strip().startswith("Number")):
|
|
21
|
+
metadata.append(line.strip())
|
|
22
|
+
line = file.readline()
|
|
23
|
+
|
|
24
|
+
# Detect delimiter and process the header
|
|
25
|
+
if ";" in line:
|
|
26
|
+
delimiter = ";"
|
|
27
|
+
elif "," in line:
|
|
28
|
+
delimiter = ","
|
|
29
|
+
else:
|
|
30
|
+
delimiter = None # Whitespace-delimited
|
|
31
|
+
|
|
32
|
+
# Read and parse data lines
|
|
33
|
+
if delimiter:
|
|
34
|
+
headers = line.strip().split(delimiter)
|
|
35
|
+
data_lines = [row.strip().split(delimiter) for row in file if row.strip()]
|
|
36
|
+
else:
|
|
37
|
+
headers = line.strip().split()
|
|
38
|
+
data_lines = [row.strip().split() for row in file if row.strip()]
|
|
39
|
+
|
|
40
|
+
# Join metadata into a single string
|
|
41
|
+
metadata_str = "\n".join(metadata)
|
|
42
|
+
|
|
43
|
+
# Create DataFrame
|
|
44
|
+
df = pd.DataFrame(data_lines, columns=headers)
|
|
45
|
+
|
|
46
|
+
return df, metadata_str
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
|
|
3
|
+
from pydpeet.io.utils.formatter_utils import nan_to_none_in_column, round_testtime, typecast
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def get_data_into_format_zahner_1(df: pd.DataFrame) -> pd.DataFrame:
|
|
7
|
+
"""
|
|
8
|
+
Rounds the values in the "Test_Time[s]" column of the DataFrame to 5 decimal places, replaces NaN values with None, and typecasts the "Step_Count" and "EIS_f[Hz]" columns to int and float, respectively.
|
|
9
|
+
|
|
10
|
+
Parameters
|
|
11
|
+
----------
|
|
12
|
+
dataFrame : pandas.DataFrame
|
|
13
|
+
DataFrame to be modified
|
|
14
|
+
|
|
15
|
+
Returns
|
|
16
|
+
-------
|
|
17
|
+
pandas.DataFrame
|
|
18
|
+
Modified DataFrame
|
|
19
|
+
"""
|
|
20
|
+
round_testtime(df)
|
|
21
|
+
nan_to_none_in_column(df, "Test_Time[s]")
|
|
22
|
+
typecast(df, "Step_Count", int)
|
|
23
|
+
typecast(df, "EIS_f[Hz]", float)
|
|
24
|
+
|
|
25
|
+
return df
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_data_into_format_zahner_2(df: pd.DataFrame) -> pd.DataFrame:
|
|
29
|
+
"""
|
|
30
|
+
Rounds the values in the "Test_Time[s]" column of the DataFrame to 5 decimal places, replaces NaN values with None, and typecasts the "Step_Count", "Voltage[V]", and "Current[A]" columns to int, float, and float, respectively.
|
|
31
|
+
|
|
32
|
+
Parameters
|
|
33
|
+
----------
|
|
34
|
+
dataFrame : pandas.DataFrame
|
|
35
|
+
DataFrame to be modified
|
|
36
|
+
|
|
37
|
+
Returns
|
|
38
|
+
-------
|
|
39
|
+
pandas.DataFrame
|
|
40
|
+
Modified DataFrame
|
|
41
|
+
"""
|
|
42
|
+
df = round_testtime(df)
|
|
43
|
+
df = nan_to_none_in_column(df, "Test_Time[s]")
|
|
44
|
+
typecast(df, "Step_Count", int)
|
|
45
|
+
typecast(df, "Voltage[V]", float)
|
|
46
|
+
typecast(df, "Current[A]", float)
|
|
47
|
+
|
|
48
|
+
return df
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_data_into_format_zahner_3(df: pd.DataFrame) -> pd.DataFrame:
|
|
52
|
+
"""
|
|
53
|
+
Rounds the values in the "Test_Time[s]" column of the DataFrame to 5 decimal places, replaces NaN values with None, and typecasts the "Step_Count", "Voltage[V]", "Current[A]", and "EIS_f[Hz]" columns to int, float, float, and float, respectively.
|
|
54
|
+
|
|
55
|
+
Parameters
|
|
56
|
+
----------
|
|
57
|
+
dataFrame : pandas.DataFrame
|
|
58
|
+
DataFrame to be modified
|
|
59
|
+
|
|
60
|
+
Returns
|
|
61
|
+
-------
|
|
62
|
+
pandas.DataFrame
|
|
63
|
+
Modified DataFrame
|
|
64
|
+
"""
|
|
65
|
+
df = round_testtime(df)
|
|
66
|
+
df = nan_to_none_in_column(df, "Test_Time[s]")
|
|
67
|
+
typecast(df, "Step_Count", int)
|
|
68
|
+
typecast(df, "Voltage[V]", float)
|
|
69
|
+
typecast(df, "Current[A]", float)
|
|
70
|
+
typecast(df, "EIS_f[Hz]", float)
|
|
71
|
+
|
|
72
|
+
return df
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Map raw-data column names (left) to standardized column names (right)
|
|
2
|
+
COLUMN_MAP_1 = {
|
|
3
|
+
r"Frequency/Hz": "EIS_f[Hz]",
|
|
4
|
+
"Number": "Step_Count",
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
COLUMN_MAP_2 = {
|
|
8
|
+
"step": "Step_Count",
|
|
9
|
+
"voltage V": "Voltage[V]",
|
|
10
|
+
"time s": "Test_Time[s]",
|
|
11
|
+
"current A": "Current[A]",
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
COLUMN_MAP_3 = {
|
|
15
|
+
"Number": "Step_Count",
|
|
16
|
+
"Voltage/V": "Voltage[V]",
|
|
17
|
+
"Current/A": "Current[A]",
|
|
18
|
+
"Time/s": "Test_Time[s]",
|
|
19
|
+
"Frequency/Hz": "EIS_f[Hz]",
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
# Default columns of the standardized format
|
|
23
|
+
# which are not present in the raw data files.
|
|
24
|
+
MISSING_REQUIRED_COLUMNS_1 = [
|
|
25
|
+
"Test_Time[s]",
|
|
26
|
+
"Voltage[V]",
|
|
27
|
+
"Current[A]",
|
|
28
|
+
"Date_Time",
|
|
29
|
+
"Temperature[°C]",
|
|
30
|
+
"EIS_DC[A]",
|
|
31
|
+
"EIS_Z_Real[Ohm]",
|
|
32
|
+
"EIS_Z_Imag[Ohm]",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
MISSING_REQUIRED_COLUMNS_2 = [
|
|
36
|
+
"EIS_DC[A]",
|
|
37
|
+
"Temperature[°C]",
|
|
38
|
+
"Date_Time",
|
|
39
|
+
"EIS_f[Hz]",
|
|
40
|
+
"EIS_Z_Real[Ohm]",
|
|
41
|
+
"EIS_Z_Imag[Ohm]",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
MISSING_REQUIRED_COLUMNS_3 = [
|
|
45
|
+
"Temperature[°C]",
|
|
46
|
+
"Date_Time",
|
|
47
|
+
"EIS_Z_Real[Ohm]",
|
|
48
|
+
"EIS_Z_Imag[Ohm]",
|
|
49
|
+
"EIS_DC[A]",
|
|
50
|
+
]
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import pandas as pd
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def to_dataframe(input_path: str) -> tuple[pd.DataFrame, str]:
|
|
5
|
+
"""
|
|
6
|
+
Parses the input file from the Zahner Cycler into a pandas DataFrame.
|
|
7
|
+
|
|
8
|
+
Parameters:
|
|
9
|
+
input_path (str): Path to the input file.
|
|
10
|
+
|
|
11
|
+
Returns:
|
|
12
|
+
(pd.DataFrame, str): A tuple containing the DataFrame with data and metadata as a string.
|
|
13
|
+
"""
|
|
14
|
+
metadata = []
|
|
15
|
+
|
|
16
|
+
# Open the file and process lines
|
|
17
|
+
with open(input_path, encoding="us-ascii") as file:
|
|
18
|
+
# Read metadata until a data header line is detected
|
|
19
|
+
line = file.readline()
|
|
20
|
+
while not (line.strip().startswith("time") or line.strip().startswith("Number")):
|
|
21
|
+
metadata.append(line.strip())
|
|
22
|
+
line = file.readline()
|
|
23
|
+
|
|
24
|
+
# Detect delimiter and process the header
|
|
25
|
+
if ";" in line:
|
|
26
|
+
delimiter = ";"
|
|
27
|
+
elif "," in line:
|
|
28
|
+
delimiter = ","
|
|
29
|
+
else:
|
|
30
|
+
delimiter = None # Whitespace-delimited
|
|
31
|
+
|
|
32
|
+
# Read and parse data lines
|
|
33
|
+
if delimiter:
|
|
34
|
+
headers = line.strip().split(delimiter)
|
|
35
|
+
data_lines = [row.strip().split(delimiter) for row in file if row.strip()]
|
|
36
|
+
else:
|
|
37
|
+
headers = line.strip().split()
|
|
38
|
+
data_lines = [row.strip().split() for row in file if row.strip()]
|
|
39
|
+
|
|
40
|
+
# Join metadata into a single string
|
|
41
|
+
metadata_str = "\n".join(metadata)
|
|
42
|
+
|
|
43
|
+
# Create DataFrame
|
|
44
|
+
df = pd.DataFrame(data_lines, columns=headers)
|
|
45
|
+
|
|
46
|
+
return df, metadata_str
|
pydpeet/io/map.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
import pandas as pd
|
|
4
|
+
|
|
5
|
+
from pydpeet.io.configs.config import STANDARD_COLUMNS
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def mapping(
|
|
9
|
+
df: pd.DataFrame,
|
|
10
|
+
column_map: dict,
|
|
11
|
+
missing_columns: list,
|
|
12
|
+
) -> pd.DataFrame:
|
|
13
|
+
"""
|
|
14
|
+
Renames and maps specific columns in the DataFrame to standardized names.
|
|
15
|
+
If a standardized column doesn't exist in the DataFrame, it is added with default None values.
|
|
16
|
+
Non-mapped columns will remain unchanged in the resulting DataFrame.
|
|
17
|
+
|
|
18
|
+
Parameters:
|
|
19
|
+
dataFrame (pandas.DataFrame): The input DataFrame to be processed.
|
|
20
|
+
column_map (dict): A dictionary mapping existing column names to new standardized names.
|
|
21
|
+
missing_columns (list): A list of column names to ensure their existence in the DataFrame.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
pandas.DataFrame: The updated DataFrame with standardized column names.
|
|
25
|
+
|
|
26
|
+
Assumptions:
|
|
27
|
+
column_map and missing_columns contain all standard columns
|
|
28
|
+
(If you want to rename more or add more columns do it after the conversion)
|
|
29
|
+
"""
|
|
30
|
+
if df is None:
|
|
31
|
+
raise ValueError("dataFrame is None")
|
|
32
|
+
if column_map is None:
|
|
33
|
+
raise ValueError("column_map is None")
|
|
34
|
+
if missing_columns is None:
|
|
35
|
+
raise ValueError("missing_columns is None")
|
|
36
|
+
if type(df) is not pd.DataFrame:
|
|
37
|
+
raise ValueError("dataFrame is not a DataFrame")
|
|
38
|
+
if type(column_map) is not dict:
|
|
39
|
+
raise ValueError("column_map is not a dictionary")
|
|
40
|
+
if type(missing_columns) is not list:
|
|
41
|
+
raise ValueError("missing_columns is not a list")
|
|
42
|
+
standard_columns_excluding_metadata = [col for col in STANDARD_COLUMNS if not col.startswith("Meta_Data")]
|
|
43
|
+
if not all(col in list(column_map.values()) + missing_columns for col in standard_columns_excluding_metadata):
|
|
44
|
+
raise ValueError("column_map and missing_columns do not contain all standard columns")
|
|
45
|
+
if any(col not in STANDARD_COLUMNS for col in list(column_map.values()) + missing_columns):
|
|
46
|
+
raise ValueError("column_map and missing_columns contain columns that are not standard columns")
|
|
47
|
+
|
|
48
|
+
# Create a copy of the DataFrame to avoid modifying the original
|
|
49
|
+
df_copy = df.copy()
|
|
50
|
+
|
|
51
|
+
# Add missing columns with None values if they do not exist, and issue warnings
|
|
52
|
+
for missing in missing_columns:
|
|
53
|
+
if missing not in df_copy.columns:
|
|
54
|
+
logging.warning(f"Missing column: '{missing}'. Adding Collumn (with None values) named: '{missing}'.")
|
|
55
|
+
df_copy[missing] = None
|
|
56
|
+
|
|
57
|
+
# Warn if a column that should be mapped is missing and add it with None values
|
|
58
|
+
for original_col in column_map.keys():
|
|
59
|
+
if original_col not in df_copy.columns:
|
|
60
|
+
logging.warning(
|
|
61
|
+
f"Column to be mapped '{original_col}' does not exist in DataFrame. Adding it with None values."
|
|
62
|
+
)
|
|
63
|
+
df_copy[original_col] = None
|
|
64
|
+
|
|
65
|
+
# Rename the existing columns as per the column_map
|
|
66
|
+
# Columns not in the column_map will remain unchanged
|
|
67
|
+
df_copy.rename(columns=column_map, inplace=True)
|
|
68
|
+
|
|
69
|
+
return df_copy
|
pydpeet/io/read.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
import pandas as pd
|
|
7
|
+
|
|
8
|
+
from pydpeet.io.configs.config import Config
|
|
9
|
+
from pydpeet.io.convert import (
|
|
10
|
+
convert_file,
|
|
11
|
+
convert_files_in_directory,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
ConfigLike = Config | str
|
|
15
|
+
PathLike = str | Path
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def read(
|
|
19
|
+
config: ConfigLike,
|
|
20
|
+
input_path: object,
|
|
21
|
+
keep_all_additional_data: bool = False,
|
|
22
|
+
custom_folder_path: str = None,
|
|
23
|
+
) -> pd.DataFrame | list[pd.DataFrame]:
|
|
24
|
+
# TODO: Docstring
|
|
25
|
+
if isinstance(input_path, str):
|
|
26
|
+
if os.path.isfile(input_path):
|
|
27
|
+
return convert_file(config, input_path, None, keep_all_additional_data, custom_folder_path)
|
|
28
|
+
elif os.path.isdir(input_path):
|
|
29
|
+
return convert_files_in_directory(config, input_path, None, keep_all_additional_data, custom_folder_path)
|
|
30
|
+
else:
|
|
31
|
+
raise ValueError("Input path is invalid!")
|
|
32
|
+
elif isinstance(input_path, list):
|
|
33
|
+
dfs = []
|
|
34
|
+
for input_item in input_path:
|
|
35
|
+
if isinstance(input_item, str):
|
|
36
|
+
if os.path.isfile(input_item):
|
|
37
|
+
dfs.append(convert_file(config, input_item, None, keep_all_additional_data, custom_folder_path))
|
|
38
|
+
elif os.path.isdir(input_item):
|
|
39
|
+
dfs.append(
|
|
40
|
+
convert_files_in_directory(
|
|
41
|
+
config, input_item, None, keep_all_additional_data, custom_folder_path
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
else:
|
|
45
|
+
raise ValueError("Input path item is invalid!")
|
|
46
|
+
else:
|
|
47
|
+
raise ValueError("Input path item is of invalid type!")
|
|
48
|
+
return dfs
|
|
49
|
+
else:
|
|
50
|
+
raise ValueError("Input path is of invalid type!")
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ExtPath(Path):
|
|
5
|
+
@staticmethod
|
|
6
|
+
def is_not_valid(path: str | None) -> bool:
|
|
7
|
+
"""
|
|
8
|
+
Checks if a given path is not valid.
|
|
9
|
+
|
|
10
|
+
Args:
|
|
11
|
+
path (str | None): The path to check.
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
bool: True if the path is not valid; False otherwise.
|
|
15
|
+
"""
|
|
16
|
+
return not ExtPath.is_valid(path)
|
|
17
|
+
|
|
18
|
+
@staticmethod
|
|
19
|
+
def is_valid(path: str | None) -> bool:
|
|
20
|
+
"""
|
|
21
|
+
Checks if a given path is valid.
|
|
22
|
+
|
|
23
|
+
A path is valid if it is not None or empty and if it exists.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
path (str | None): The path to check.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
bool: True if the path is valid; False otherwise.
|
|
30
|
+
"""
|
|
31
|
+
return bool(path) and Path(path).exists()
|
|
32
|
+
|
|
33
|
+
def is_excel_file(self) -> bool:
|
|
34
|
+
return self.exists() and self.is_file() and self.suffix in (".xlsx", ".xls")
|