#!/usr/bin/env python
import functools
from typing import Optional
import pandas as pd
from .age import EstimatedAge
from .context import Context
from .joints import Joints
from .left_right import LeftRight
from .mouth import Mouth
from .occupational_markers import OccupationalMarkers
from .sex import Sex
from .trauma import Trauma
# Notes:
# * https://www.archaeologists.net/sites/default/files/ifa_paper_7.pdf
[docs]class BurialInfo(object):
"""docstring for BurialInfo"""
def __init__(self, site_name: str, site_id: str):
if site_name is None or site_id is None:
raise ValueError('site_name and site_id required')
if site_name == '' or site_id == '':
raise ValueError('site_name and site_id required')
self.name = site_name
self.id = site_id
[docs] def to_pd_series(self, prefix=''):
labels = [f'{prefix}{label}' for label in ['name', 'id']]
return pd.Series([self.name, self.id], index=labels, copy=True)
[docs]@functools.total_ordering
class LongBoneMeasurement(object):
"""docstring for LongBoneMeasurement"""
def __init__(self, _max: Optional[float], bi: Optional[float], head: Optional[float], distal: Optional[float]):
self.max = _max
self.bi = bi
self.head = head
self.distal = distal
[docs] @staticmethod
def empty():
return LongBoneMeasurement(None, None, None, None)
[docs] @staticmethod
def empty_lr():
return LeftRight(LongBoneMeasurement.empty(), LongBoneMeasurement.empty())
[docs] def to_pd_series(self, prefix=''):
labels = [f'{prefix}{label}' for label in ['max', 'bi', 'head', 'distal']]
return pd.Series([self.max, self.bi, self.head, self.distal], index=labels, copy=True)
def __eq__(self, other):
if type(other) != type(self): # pylint: disable=C0123
raise NotImplementedError
return ((self.max, self.bi, self.head, self.distal) == (other.max, other.bi, other.head, other.distal)) # pylint: disable=C0325
def __lt__(self, other):
if type(other) != type(self): # pylint: disable=C0123
raise NotImplementedError
return ((self.max, self.bi, self.head, self.distal) < (other.max, other.bi, other.head, other.distal)) # pylint: disable=C0325
[docs]class OsteologicalSex(object):
"""docstring for OsteologicalSex"""
def __init__(self, pelvic: Optional[Sex], cranium: Optional[Sex], combined: Optional[Sex]):
self.pelvic = pelvic
self.cranium = cranium
self.combined = combined
[docs] @staticmethod
def empty():
return OsteologicalSex(None, None, None)
[docs] def to_pd_data_frame(self, index):
d = {
'id': pd.Series([index]),
}
for l in ('pelvic', 'cranium', 'combined'):
val = getattr(self, l)
d[f'{l}_cat'] = pd.Series([val.name if val else None], copy=True, dtype=Sex.dtype()) # noqa: E241
d[f'{l}_val'] = pd.Series([val.value if val else None], copy=True, dtype='Int64')
val_bin = val.as_bin() if val else None
d[f'{l}_bin_cat'] = pd.Series([val_bin.name if val_bin else None], copy=True, dtype=Sex.dtype()) # noqa: E241
d[f'{l}_bin_val'] = pd.Series([val_bin.value if val_bin else None], copy=True, dtype='Int64')
return pd.DataFrame.from_dict(d).set_index('id')
[docs]class AgeSexStature(object):
"""docstring for AgeSexStature"""
__slots__ = ['osteological_sex', 'age', 'femur', 'humerus', 'tibia', 'stature', 'body_mass']
def __init__(self, osteological_sex: OsteologicalSex, age: EstimatedAge, femur: LongBoneMeasurement, humerus: LongBoneMeasurement, tibia: LongBoneMeasurement, stature: Optional[str], body_mass: Optional[str]):
# Sex
self.osteological_sex = osteological_sex
# Age
self.age = age
# Long bones
self.femur = femur
self.humerus = humerus
self.tibia = tibia
# Other
self.stature = stature
self.body_mass = body_mass
[docs] @staticmethod
def empty():
return AgeSexStature(OsteologicalSex.empty(), EstimatedAge.empty(), LongBoneMeasurement.empty_lr(), LongBoneMeasurement.empty_lr(), LongBoneMeasurement.empty_lr(), None, None)
[docs] def to_pd_data_frame(self, index):
data = {
'id': pd.Series([index]),
'stature': pd.Series([self.stature]),
'body_mass': pd.Series([self.body_mass]),
}
df = pd.DataFrame.from_dict(data).set_index('id')
s = pd.Series([])
for bone in ('femur', 'humerus', 'tibia'):
lr_val = getattr(self, bone)
s = s.append(lr_val.left.to_pd_series(prefix=f'{bone}_left_'))
s = s.append(lr_val.right.to_pd_series(prefix=f'{bone}_right_'))
s = s.append(lr_val.avg().to_pd_series(prefix=f'{bone}_avg_'))
long_bones = pd.DataFrame.from_dict({index: s}, orient='index')
age = self.age.to_pd_data_frame(index).add_prefix(f'age_').rename(columns={f'age_id': 'id'})
oss = self.osteological_sex.to_pd_data_frame(index).add_prefix(f'osteological_sex_').rename(columns={f'osteological_sex_id': 'id'})
return df.join(long_bones, on='id', how='outer') \
.join(age, on='id', how='outer') \
.join(oss, on='id', how='outer')
[docs]class Individual(object):
"""docstring for Individual"""
def __init__(self, _id: str, site: BurialInfo, age_sex_stature: AgeSexStature, mouth: Mouth, occupational_markers: OccupationalMarkers, joints: Joints, trauma: Trauma, context: Context):
self.id = _id
self.site = site
self.age_sex_stature = age_sex_stature
self.mouth = mouth
self.occupational_markers = occupational_markers
self.joints = joints
self.trauma = trauma
self.context = context
[docs] def to_pd_data_frame(self):
s = pd.Series([self.id], index=['id'], copy=True)
s = s.append(self.site.to_pd_series(prefix='site_'))
s = s.append(self.mouth.to_pd_series(prefix='mouth_'))
joints_df = self.joints.to_pd_data_frame(self.id).add_prefix('joints_').rename(columns={f'joints_id': 'id'})
ass_df = self.age_sex_stature.to_pd_data_frame(self.id).add_prefix('ass_').rename(columns={f'ass_id': 'id'})
om_df = self.occupational_markers.to_pd_data_frame(self.id).add_prefix('om_').rename(columns={f'om_id': 'id'})
trauma_df = self.trauma.to_pd_data_frame(self.id).add_prefix('trauma_').rename(columns={f'trauma_id': 'id'})
context_df = self.context.to_pd_data_frame(self.id).add_prefix('context_').rename(columns={f'context_id': 'id'})
df = pd.DataFrame.from_dict({self.id: s}, orient='index')
return df.join(joints_df, on='id', how='outer') \
.join(ass_df, on='id', how='outer') \
.join(om_df, on='id', how='outer') \
.join(trauma_df, on='id', how='outer') \
.join(context_df, on='id', how='outer')
if __name__ == "__main__":
raise RuntimeError('No main available')