Adding classes for external SSE datasets - #3
Conversation
|
@leah-mungai Can you add a short description of what the PR does? |
ftherrien
left a comment
There was a problem hiding this comment.
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. |
…rrors. Updated data paths and how data is downloaded. Changes made to both class LiIon and Laskowski.
| dataframe (pd.DataFrame): DataFrame containing the dataset. | ||
| ''' | ||
|
|
||
| def __init__(self, data_path="/home/leah/leah---OBELiX/data/Shon_and_Min.xlsx"): |
There was a problem hiding this comment.
The datapath here should be something like rawdata_SM
| 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) |
There was a problem hiding this comment.
I don't think I would put drop duplicates here because it does not really relate to "room temperature only"
…Shon and Min dataset
…hods: remove_obelix, and get_unique_compositions.
ftherrien
left a comment
There was a problem hiding this comment.
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
| to_drop.append(idx) | ||
| continue | ||
|
|
||
| df_clean = df.drop(index=to_drop).reset_index(drop=True) |
There was a problem hiding this comment.
I would keep the original index here
| 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)"]) |
There was a problem hiding this comment.
I would not apply log10 here because we don't apply it for other dataset, we can let users decide to do that
| return False | ||
|
|
||
| # Main processing pipeline | ||
| def main(input_xlsx: str = "ao3c01424_si_001.xlsx", |
There was a problem hiding this comment.
Maybe rename this function to something that explains what it does, like `clean_shon_min
| # 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 | ||
|
|
| @@ -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): | |||
There was a problem hiding this comment.
Add an argument to choose to remove room temperatue only and make it true by default
| 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. |
There was a problem hiding this comment.
"Loads and cleans the Shon and Min dataset from [the url of the excel sheet]"
| 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") |
There was a problem hiding this comment.
Replace this by the url. This would only work on your computer anyway since it is an absolute path
There was a problem hiding this comment.
Replace this by a URL to our repo
There was a problem hiding this comment.
Make it a link to your Github for now and we will change it once we merge into main here
| 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 |
There was a problem hiding this comment.
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
| ''' | ||
|
|
||
| def __init__(self, data_path="/home/leah/leah---OBELiX/data/Shon_and_Min.xlsx"): | ||
| def __init__(self, data_path="./SM_rawdata"): |
There was a problem hiding this comment.
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.
| 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)] |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
|
|
||
| return df_clean | ||
|
|
||
| def remove_obelix(OBELiX, LiIon, Laskowski): |
There was a problem hiding this comment.
Make this a method of LiIon and Laskowski:
def remove_obelix(self, obelix_object=Obelix())
Usage:
la = Laskowski()
la_no_obelix = la.remove_obelix()
|
|
||
| return filtered_liion, filtered_laskowski | ||
|
|
||
| def get_unique_compositions(OBELiX, LiIon, Laskowski, ShonAndMin): |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
|
|
||
| all_compositions = pd.concat([ob_compositions, li_compositions, la_compositions, sm_compositions]).drop_duplicates() | ||
|
|
||
| return all_compositions.sort_values().reset_index(drop=True) |
There was a problem hiding this comment.
Don't forget to keep ionic conductivity
…Laskowski to automatically remove obelix entries
… ShonAndMin datasets
…plicate as an attribute
… add_DOIs method to Laskowski class
|
|
||
| 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) |
There was a problem hiding this comment.
I think here you only need:
return self.dataframe.drop[liion_ids]
…ula. Added a new method to laskowski to find entries where the compositions matches between obelix and laskowski but atleast one of the DOI is missing
…ethods for Laskowski
…ies. Updated test_ShonAndMin to keep minimum conductivity for duplicated entries.
…Updated class ShonAndMin to keep minimum conductivity for duplicated entries.
…dataset class, and shonandmin.py with ShonAndMin dataset class.
…e other test methods.
| 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 |
There was a problem hiding this comment.
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
| self.dataframe = self.dataframe.drop(index=indices_to_remove) | ||
| return self.dataframe |
There was a problem hiding this comment.
Return a new Dataset object here:
new_dataframe = self.dataframe.drop(index=indices_to_remove)
return Dataset(new_dataframe)
| 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) |
There was a problem hiding this comment.
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.
- 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>
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:
LiIonLaskowskiShonAndMinTo do:
remove_OBELiX. Using the tow columns:Laskowski IDandLiIon IDDatasetpytestFor 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.