Skip to content

Commit 07cc3c9

Browse files
committed
2 parents d334397 + 8e43419 commit 07cc3c9

3 files changed

Lines changed: 81 additions & 30 deletions

File tree

dataria/DATA.py

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,4 +153,29 @@ def parse_xsd_date_or_datetime(iso_string, dtype, unix_year=1950):
153153

154154
except Exception as e:
155155
print(f"Error parsing '{dtype}' with value '{iso_string}': {e}")
156-
return pd.NaT # Fallback to the original string
156+
return pd.NaT # Fallback to the original string
157+
158+
def get_token_matrix(series, sep=" ", dummies=True):
159+
"""
160+
Generate a token matrix (either binary or count-based) from a Pandas Series.
161+
162+
Args:
163+
series (pd.Series): The input column containing string data.
164+
sep (str): Separator used to split tokens.
165+
dummies (bool): If True, return binary (0/1) presence; if False, return token counts.
166+
167+
Returns:
168+
pd.DataFrame: A DataFrame with one column per token and one row per entry.
169+
"""
170+
series = series.fillna("").astype(str)
171+
172+
if dummies:
173+
return series.str.get_dummies(sep=sep)
174+
else:
175+
return (
176+
series
177+
.str.split(sep)
178+
.apply(lambda tokens: pd.Series(tokens).value_counts())
179+
.fillna(0)
180+
.astype(int)
181+
)

dataria/MATH.py

Lines changed: 54 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@
55
import numpy as np
66
import upsetplot as up
77
from rapidfuzz import fuzz
8-
from .DATA import sparql_to_dataframe
8+
from .DATA import sparql_to_dataframe, get_token_matrix
99

1010
def correlation(df=None,
1111
endpoint_url=None,
1212
query=None,
13-
col1=None, col2=None, sep=';', edges=0, csv_filename="correlations.csv", heatmap=True, heatmap_kwargs={}, save_PNG=True, verbose=True):
13+
col1=None, col2=None, sep=';', edges=0, csv_filename="correlations.csv", heatmap=True, heatmap_kwargs={}, dummies_matrix=True, save_PNG=True, verbose=True):
1414
"""
1515
Compute correlations between two columns of a DataFrame, including support for categorical (string) data.
1616
@@ -27,6 +27,7 @@ def correlation(df=None,
2727
csv_filename (str, optional): File path to save the result as CSV.
2828
heatmap (bool, optional): Whether to generate a heatmap. Default is True.
2929
heatmap_kwargs (dict, optional): Additional kwargs passed to the heatmap function.
30+
dummies_matrix (bool, optional): Treat col1 and col2 as binary dummies. Default is True, else bag of words, count frequency.
3031
save_PNG (bool, optional): Whether to save the heatmap as a PNG file.
3132
verbose (bool, optional): Whether to print insights into the dataframe.
3233
@@ -55,8 +56,9 @@ def correlation(df=None,
5556
return pd.DataFrame({'Correlation': [correlation], 'P-Value': [p_val]}, index=[f'{col1} vs {col2}'])
5657
elif pd.api.types.is_string_dtype(df[col1]):
5758
# Both are string, create dummy variables once
58-
dummies = df[col1].str.get_dummies(sep=sep)
59-
59+
dummies = get_token_matrix(df[col1],sep,dummies_matrix)
60+
# dummies = df[col1].str.get_dummies(sep=sep)
61+
6062
if dummies.shape[1] < 2:
6163
raise ValueError(f"Not enough dummy variables in '{col1}' to calculate correlations.")
6264

@@ -70,12 +72,12 @@ def correlation(df=None,
7072
# Case: Both columns are different
7173

7274
if pd.api.types.is_string_dtype(df[col1]):
73-
col1_dummies_raw = df[col1].astype(str).str.get_dummies(sep=sep)
75+
col1_dummies_raw = get_token_matrix(df[col1],sep,dummies_matrix) # df[col1].astype(str).str.get_dummies(sep=sep)
7476
else:
7577
col1_dummies_raw = df[[col1]].astype(float)
7678

7779
if pd.api.types.is_string_dtype(df[col2]):
78-
col2_dummies_raw = df[col2].astype(str).str.get_dummies(sep=sep)
80+
col2_dummies_raw = get_token_matrix(df[col2],sep,dummies_matrix) # df[col2].astype(str).str.get_dummies(sep=sep)
7981
else:
8082
col2_dummies_raw = df[[col2]].astype(float)
8183

@@ -256,9 +258,10 @@ def upset(
256258
return df_final
257259

258260
def fuzzy_compare(df1=None,df2=None,
259-
endpoint_url=None,
260-
query=None,
261-
grouping_var=None, label_var=None, element_var=None, threshold=95, match_all=False, unique_rows=False, csv_filename="comparison.csv", verbose= True):
261+
additional_vars_df1=None, additional_vars_df2=None,
262+
endpoint_url=None,
263+
query=None,
264+
grouping_var=None, label_var=None, element_var=None, threshold=95, match_all=False, unique_rows=False, csv_filename="comparison.csv", verbose= True):
262265
"""
263266
Fuzzy string matching between two DataFrames (or SPARQL query results) based on a common element column.
264267
@@ -267,6 +270,8 @@ def fuzzy_compare(df1=None,df2=None,
267270
Args:
268271
df1 (pd.DataFrame, optional): First DataFrame.
269272
df2 (pd.DataFrame, optional): Second DataFrame. If not provided, df1 is used.
273+
additional_vars_df1 (list, optional): List of columns that will be aggregated in the result (using first per group).
274+
additional_vars_df2 (list, optional): List of columns that will be aggregated in the result (using first per group).
270275
endpoint_url (str, optional): SPARQL endpoint.
271276
query (str, optional): SPARQL query.
272277
grouping_var (str, optional): Column name used for grouping.
@@ -283,23 +288,28 @@ def fuzzy_compare(df1=None,df2=None,
283288
"""
284289

285290

286-
if df1 is None and endpoint_url and query:
287-
try:
288-
# Fetch data and create DataFrame
289-
df1 = sparql_to_dataframe(endpoint_url, query, csv_filename=f"query1_{csv_filename}" if csv_filename is not None else None)
290-
except Exception as e:
291-
raise ValueError(f"Failed to fetch or process SPARQL query results. Error: {e}")
292291

293-
if df2 is None and endpoint_url and query:
292+
if df1 is None and endpoint_url and query:
294293
try:
295294
# Fetch data and create DataFrame
296-
df2 = sparql_to_dataframe(endpoint_url, query, csv_filename=f"query2_{csv_filename}" if csv_filename is not None else None)
295+
df1 = sparql_to_dataframe(endpoint_url, query, csv_filename=f"query_{csv_filename}" if csv_filename is not None else None)
297296
except Exception as e:
298297
raise ValueError(f"Failed to fetch or process SPARQL query results. Error: {e}")
299298

300299
if df2 is None:
301300
df2 = df1
302301

302+
303+
if additional_vars_df1 is None:
304+
additional_vars_df1 = []
305+
else:
306+
additional_vars_df1 = [col for col in additional_vars_df1 if col in df1.columns]
307+
308+
if additional_vars_df2 is None:
309+
additional_vars_df2 = []
310+
else:
311+
additional_vars_df2 = [col for col in additional_vars_df2 if col in df2.columns]
312+
303313
# Validate that columns exist in the DataFrame
304314
if element_var not in df1.columns or element_var not in df2.columns:
305315
raise ValueError(f"Column '{element_var}' do not exist in the DataFrame.")
@@ -344,14 +354,22 @@ def fuzzy_compare(df1=None,df2=None,
344354
else:
345355
score = fuzz.ratio(row2[element_var].lower(), row1[element_var].lower())
346356

347-
matches.append({
357+
match_entry = {
348358
'group2': group_key2,
349359
'group1': group_key1,
350360
'label': row2[label_var] if check_label else None,
351361
'element2': row2[element_var],
352362
'element1': row1[element_var],
353363
'score': score
354-
})
364+
}
365+
366+
for col in additional_vars_df1:
367+
match_entry[f'df1_{col}'] = row1[col]
368+
369+
for col in additional_vars_df2:
370+
match_entry[f'df2_{col}'] = row2[col]
371+
372+
matches.append(match_entry)
355373

356374
matches_df = pd.DataFrame(matches)
357375
aggregated = pd.DataFrame()
@@ -363,15 +381,23 @@ def fuzzy_compare(df1=None,df2=None,
363381
matches_df = matches_df[matches_df['score'] >= threshold]
364382

365383
if not matches_df.empty:
366-
aggregated = matches_df.groupby(['group1', 'group2']).agg(
367-
Labels=('label', lambda x: ", ".join(sorted(set(x)))),
368-
df1_Elements=('element1', lambda x: ", ".join(sorted(set(x)))),
369-
df2_Elements=('element2', lambda x: ", ".join(sorted(set(x)))),
370-
Num_Matches=('score', 'count'),
371-
Average_Score=('score', 'mean'),
372-
Min_Score=('score', 'min'),
373-
Max_Score=('score', 'max')
374-
).reset_index()
384+
agg_dict = {
385+
'Labels': ('label', lambda x: ", ".join(sorted(set(x)))),
386+
'df1_Elements': ('element1', lambda x: ", ".join(sorted(set(x)))),
387+
'df2_Elements': ('element2', lambda x: ", ".join(sorted(set(x)))),
388+
'Num_Matches': ('score', 'count'),
389+
'Average_Score': ('score', 'mean'),
390+
'Min_Score': ('score', 'min'),
391+
'Max_Score': ('score', 'max')
392+
}
393+
394+
for col in additional_vars_df1:
395+
agg_dict[f'df1_{col}'] = (f'df1_{col}', 'first')
396+
for col in additional_vars_df2:
397+
agg_dict[f'df2_{col}'] = (f'df2_{col}', 'first')
398+
399+
aggregated = matches_df.groupby(['group1', 'group2']).agg(**agg_dict).reset_index()
400+
375401
if verbose:
376402
print(aggregated.info())
377403
aggregated.describe()

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "dataria"
7-
version = "0.4.0"
7+
version = "0.4.1"
88
description = "DATAria utils"
99
authors = [
1010
{ name = "Christoph Sander" }

0 commit comments

Comments
 (0)