Skip to content

Adding classes for external SSE datasets - #3

Open
leah-mungai wants to merge 37 commits into
NRC-Mila:mainfrom
leah-mungai:new_taset_classes
Open

Adding classes for external SSE datasets#3
leah-mungai wants to merge 37 commits into
NRC-Mila:mainfrom
leah-mungai:new_taset_classes

Conversation

@leah-mungai

@leah-mungai leah-mungai commented Mar 26, 2025

Copy link
Copy Markdown

This pull request introduces the multipl new classes. The class will include methods that remove entries already present in OBELiX to prevent duplicate records.

New classes:

  • LiIon
  • Laskowski
  • ShonAndMin

To do:

  • Add filters to ShonandMin using @jamalabouhaibeh's script
  • For Laskowski and LiIon, make a method called remove_OBELiX. Using the tow columns: Laskowski ID and LiIon ID
  • Methods to combine all datasets composition only without duplicates: implement the merge method from pandas in Dataset
  • Methods to combine all datasets composition + spg without duplicates
  • Add test for LiIon in tests/. Test them using pytest
  • Add test for Laskowski
  • Add test for Shon&Min
  • Add tests for different methods (addition)

For the last two points it is a good idea o make a script that creates a datatset of all compositions and ICs at RT from all 4 dataset. This will be a good guide for what we need and will be a good tutorial later.

@ftherrien

Copy link
Copy Markdown
Contributor

@leah-mungai Can you add a short description of what the PR does?

@ftherrien ftherrien left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great job overall thanks!

Comment thread README.md Outdated
Comment thread obelix/__init__.py Outdated
Comment thread obelix/__init__.py Outdated
Comment thread obelix/__init__.py Outdated
Comment thread obelix/__init__.py Outdated
Comment thread obelix/__init__.py Outdated
Comment thread obelix/__init__.py Outdated
Comment thread obelix/__init__.py Outdated
@leah-mungai

Copy link
Copy Markdown
Author

Great job overall thanks!

Thank you Felix. I have made the changes as requested. The answer as to why I was catching and re-raising the error is that, initially when reading the dataset_url I was not including the raw file content format at the start of my url and so my pandas was giving me errors, but I corrected that.

Comment thread data/Shon_and_Min.xlsx
Comment thread README.md
Comment thread obelix/__init__.py Outdated
dataframe (pd.DataFrame): DataFrame containing the dataset.
'''

def __init__(self, data_path="/home/leah/leah---OBELiX/data/Shon_and_Min.xlsx"):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The datapath here should be something like rawdata_SM

Comment thread obelix/__init__.py Outdated
Comment thread obelix/utils.py Outdated
pd.DataFrame: Filtered DataFrame.
"""
filtered_df = df[(df[temp_col] >= room_temp - tolerance) & (df[temp_col] <= room_temp + tolerance)]
return filtered_df.drop_duplicates(subset=subset)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think I would put drop duplicates here because it does not really relate to "room temperature only"

@ftherrien ftherrien changed the title Added class LiIon Adding classes for external SSE datasets Apr 22, 2025

@ftherrien ftherrien left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you download it directly from the SI of the paper, I would prefer not to host our own version:

https://pubs.acs.org/doi/suppl/10.1021/acsomega.3c01424/suppl_file/ao3c01424_si_001.xlsx

Comment thread obelix/shon_min.py Outdated
to_drop.append(idx)
continue

df_clean = df.drop(index=to_drop).reset_index(drop=True)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would keep the original index here

Comment thread obelix/shon_min.py Outdated
df_clean = df.drop(index=to_drop).reset_index(drop=True)

# 5) Compute log10 target
df_clean["log_target"] = np.log10(df_clean["Ionic Conductivity Numeric (S/cm)"])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would not apply log10 here because we don't apply it for other dataset, we can let users decide to do that

Comment thread obelix/shon_min.py Outdated
return False

# Main processing pipeline
def main(input_xlsx: str = "ao3c01424_si_001.xlsx",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe rename this function to something that explains what it does, like `clean_shon_min

Comment thread obelix/__init__.py Outdated
Comment on lines +252 to +257
# def read_data(self, data_path, no_cifs=False):
# '''Reads the LiIon dataset.'''
# data = pd.read_csv(self.data_path / "LiIonDatabase.csv", index_col="ID")

# return data

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove this

Comment thread obelix/__init__.py Outdated
@@ -231,11 +234,27 @@ def download_data(self, output_path, commit_id=None, local=False):
df.to_csv(output_path / "LiIonDatabase.csv")

def read_data(self, data_path, no_cifs=False):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add an argument to choose to remove room temperatue only and make it true by default

Comment thread obelix/__init__.py Outdated
def __init__(self, data_path="./SM_rawdata"):
'''
Loads the Shon and Min dataset from an Excel file.
Loads and cleans the Shon and Min dataset from Sheet2.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Loads and cleans the Shon and Min dataset from [the url of the excel sheet]"

Comment thread obelix/__init__.py Outdated
data = pd.read_excel(data_path)
self.data_path.mkdir(parents=True, exist_ok=True)

self.source_file = Path("/home/leah/leah---OBELiX/data/ao3c01424_si_001.xlsx")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace this by the url. This would only work on your computer anyway since it is an absolute path

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replace this by a URL to our repo

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make it a link to your Github for now and we will change it once we merge into main here

Comment thread obelix/__init__.py Outdated
Comment on lines +326 to +353
def _clean_data(self, df):
'''Processes and cleans the raw dataframe.'''

df["Ionic Conductivity"] = df["Ionic Conductivity"].apply(normalize_string)

df["Ionic Conductivity Numeric"] = df["Ionic Conductivity"].apply(convert_scientific_string)

df["Ionic Conductivity Numeric (S/cm)"] = df.apply(
lambda r: convert_to_S_cm(r.get("Raw_unit", ""), r["Ionic Conductivity Numeric"]),
axis=1
)

to_drop = []
for idx, row in df.iterrows():
cond = row["Ionic Conductivity Numeric (S/cm)"]
if pd.isna(cond) or cond <= 0 or not (-18 <= np.log10(cond) <= 0):
to_drop.append(idx)
continue
name = row.get("Name", "")
if pd.isna(name) or not is_valid_formula(name):
to_drop.append(idx)
continue

df_clean = df.drop(index=to_drop).reset_index(drop=True)

df_clean["log_target"] = np.log10(df_clean["Ionic Conductivity Numeric (S/cm)"])

return df_clean

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the same as main in shon_min.py. You can just rename main there and import directly, you don't need to copy it here

Comment thread obelix/__init__.py Outdated
'''

def __init__(self, data_path="/home/leah/leah---OBELiX/data/Shon_and_Min.xlsx"):
def __init__(self, data_path="./SM_rawdata"):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would add an argument to choose to clean the data (e.g. clean_data = True) and set it to true by default so that if someone wants the raw dataset they can have it.

Comment thread obelix/__init__.py Outdated
filtered_obelix_liion = ob.dataframe[ob.dataframe['Liion ID'].isin(obelix_liion_ids)]
reduced_compositions_liion = filtered_obelix_liion['Reduced Composition'].dropna().unique()

filtered_liion = li.dataframe[~li.dataframe['composition'].isin(reduced_compositions_liion)]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to use composition because you already have the ids in obelix_liion_ids. filtered_liion = li.dataframe.loc[~obelix_liion_ids] or something like that

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The LiIon dataset doesn’t contain a Liion ID column, so although I can identify relevant Liion IDs in the Obelix dataset, I can’t directly use them to filter entries in LiIon. That’s why I opted to use the composition column in LiIon and match it with the reduced composition

Comment thread obelix/__init__.py Outdated

return df_clean

def remove_obelix(OBELiX, LiIon, Laskowski):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make this a method of LiIon and Laskowski:

def remove_obelix(self, obelix_object=Obelix())

Usage:

la = Laskowski()
la_no_obelix = la.remove_obelix()

Comment thread obelix/__init__.py Outdated

return filtered_liion, filtered_laskowski

def get_unique_compositions(OBELiX, LiIon, Laskowski, ShonAndMin):

@ftherrien ftherrien Apr 29, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be cool to implement _add_() methods. It would make most sense as a method of the Dataset class

def _add_(self, dataset):
if "Space group #" in self.dataframe and "Space group #" in dataset.dataframe:
    return Dataset(pd.concatenate([self.dataframe.loc[["Composition","Space group #", "Ionic Conductivity"]], dataset.loc[["Composition","Space group #", "Ionic Conductivity"]]))
else:
   return Dataset(pd.concatenate([self.dataframe.loc[["Composition", "Ionic Conductivity"]], dataset.loc[["Composition", "Ionic Conductivity"]]))

Usage:

data = ob + la + li

data is a dataframe with unique sets of composition and IC.

You could also have union where union would remove duplicates.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since each dataset uses different column names for composition and ionic conductivity, would it be better to rename the relevant columns within each dataset class during initialization, or should I handle all the renaming centrally within the add method before combining the datasets?

Comment thread obelix/__init__.py Outdated

all_compositions = pd.concat([ob_compositions, li_compositions, la_compositions, sm_compositions]).drop_duplicates()

return all_compositions.sort_values().reset_index(drop=True)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't forget to keep ionic conductivity

Comment thread obelix/__init__.py Outdated
Comment on lines +281 to +285

liion_ids = liion_ids[liion_ids < len(self.dataframe)]

rows_to_drop = self.dataframe.iloc[liion_ids]
return self.dataframe.drop(index=rows_to_drop.index)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think here you only need:
return self.dataframe.drop[liion_ids]

Comment thread obelix/dataset.py
Comment on lines +123 to +127
for j, test_row in self.dataframe.iterrows():
test_doi = test_row.get('DOI')
if is_same_formula(test_row['Reduced Composition'], self_comp) and test_doi == self_doi:
indices_to_remove.add(j)
break

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could replace:

                        for j, test_row in self.dataframe.iterrows():
                            test_doi = test_row.get('DOI')
                            if is_same_formula(test_row['Reduced Composition'], self_comp) and test_doi == self_doi:
                                indices_to_remove.add(j)
                        break

with only

indices_to_remove.add(i)

If you want to find entries with same composition where there is no DOI then I think you need:

                        for j, test_row in self.dataframe.iterrows():
                            test_doi = test_row.get('DOI')
                            if is_same_formula(test_row['Reduced Composition'], self_comp) and test_doi is None:
                                indices_to_remove.add(j)
                        break

Comment thread obelix/dataset.py Outdated
Comment on lines +132 to +133
self.dataframe = self.dataframe.drop(index=indices_to_remove)
return self.dataframe

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Return a new Dataset object here:

new_dataframe = self.dataframe.drop(index=indices_to_remove)
return Dataset(new_dataframe)

Comment thread obelix/liion.py Outdated
ob_df = obelix_object.dataframe
liion_ids = ob_df["Liion ID"].dropna().astype(int)

self.dataframe.loc[self.dataframe.index.intersection(liion_ids)].to_csv('liion_obelix_matching_entries.csv', index=False)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an indentation problem here?

…s and same composition. The method also keeps the original data and return the cleaned copy as a new Dataset.
@ftherrien ftherrien assigned ftherrien and unassigned ftherrien Feb 24, 2026
@josephdviviano josephdviviano self-assigned this Feb 24, 2026
josephdviviano added a commit to josephdviviano/OBELiX that referenced this pull request Feb 25, 2026
- test_utils.py: 18 unit tests for is_same_formula and replace_text_IC
- test_dataset.py: 46 unit tests for Dataset base class (synthetic data only)
- test_datasets.py: 23 integration tests for LiIon, Laskowski, ShonAndMin
- test_performance.py: 5 performance tests marked @pytest.mark.slow
- conftest.py: 9 shared fixtures for edge cases and dedup testing
- Register slow marker in pyproject.toml

Tests target the planned API (TDD): __add__, union, remove_obelix on
Laskowski/ShonAndMin will fail until implementation is complete.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants