Source code for mendeleev.utils
"""Utility functions for Mendeleev package."""
# TODO: can be removed after dropping support for python 3.8
from __future__ import annotations
from typing import Tuple
import math
import pandas as pd
from sqlalchemy.exc import SQLAlchemyError
from mendeleev.db import get_session
[docs]
def coeffs(a: int, b: int = 2) -> Tuple[int, int]:
"""
Return stoichometric coefficients from oxidation states
Args:
a: oxidation state of the first element
b: oxidation state of the second element
"""
lcm = abs(a * b) // math.gcd(a, b)
return lcm // a, lcm // b
[docs]
def render_rst_table(df: pd.DataFrame) -> str:
"""
Converts a pandas DataFrame to a reStructuredText table.
Args:
df (pd.DataFrame): The DataFrame to convert.
Returns:
str: The DataFrame as a reStructuredText table.
"""
# Get the column headers
headers = df.columns.tolist()
# Get the lengths of each column for formatting
col_lengths = [
max(len(str(val)) for val in df[col].tolist() + [col]) for col in headers
]
# Create the horizontal line for the table
hline = "+" + "+".join(["-" * (length + 2) for length in col_lengths]) + "+"
header_hline = "+" + "+".join(["=" * (length + 2) for length in col_lengths]) + "+"
# Format the header row
header_row = (
"|"
+ "|".join(
[
f" {headers[i]}{' ' * (col_lengths[i] - len(headers[i]))} "
for i in range(len(headers))
]
)
+ "|"
)
data_rows = []
for _, row in df.iterrows():
data_row = (
"|"
+ "|".join(
[
f" {str(row[col])}{' ' * (col_lengths[i] - len(str(row[col])))} "
for i, col in enumerate(headers)
]
)
+ "|"
)
data_rows.extend((data_row, hline))
return "\n".join([hline, header_row, header_hline] + data_rows)
[docs]
def update_model_from_df(
model,
data_frame: pd.DataFrame,
attributes: list[str],
primary_key: str = "atomic_number",
dropna: bool = True,
):
"""Update database table from a pandas dataframe
Args:
model: SQLAlchemy model to be updated
data_frame: pandas DataFrame with actual data
attributes: names of attributes of `model` to be updated. The attributes must exist on `model` and corresponding columns must be present in `data_frame`
primary_key: Attribute name of model and column name in `data_frame` on which instanced of `model` will be matched with rows from `data_frame`
dropna: If True, drop rows with NaN values in `attributes` before updating the database
"""
# check if model has attributes
if not all(map(lambda attr: hasattr(model, attr), attributes)):
raise ValueError(f"Model {model} does not have attribute/s.")
session = get_session(read_only=False)
for _, row in data_frame.iterrows():
if dropna:
update_values = row[attributes].dropna().to_dict()
else:
update_values = row[attributes].to_dict()
print(update_values)
if update_values: # when dict isn't empty
try:
session.query(model).filter(
getattr(model, primary_key) == row[primary_key]
).update(update_values)
session.commit()
except SQLAlchemyError as e:
print(f"ERROR for {row[primary_key]}", e)
session.rollback()
session.close()