Source code for bioarch.context

#!/usr/bin/env python


import enum
from enum import Enum
import functools
import logging
from typing import Any, cast, Dict, Optional


import pandas as pd
from pandas.api.types import CategoricalDtype


logger = logging.getLogger(__name__)


[docs]@functools.total_ordering @enum.unique class CompassBearing(Enum): NORTH = 0 # noqa: E221 NORTH_EAST = 1 EAST = 2 # noqa: E221 SOUTH_EAST = 3 SOUTH = 4 # noqa: E221 SOUTH_WEST = 5 WEST = 6 # noqa: E221 NORTH_WEST = 7
[docs] @staticmethod def parse(value: Any) -> Optional['CompassBearing']: if value is None: return None if type(value) == CompassBearing: # pylint: disable=C0123 return cast(CompassBearing, value) if not isinstance(value, str): raise ValueError(f'Failed to parse {CompassBearing.__name__}: "{value}"') value = value.upper() for bearing in CompassBearing: if value == bearing.name: return bearing if value == bearing.to_short_code(): return bearing raise ValueError(f'Failed to parse {CompassBearing.__name__}: "{value}"')
[docs] def to_short_code(self): return ''.join([p[0] for p in str(self.name).split('_')])
def __lt__(self, other): if other is None: return False if isinstance(other, int): other = CompassBearing(other) if type(other) != type(self): # pylint: disable=C0123 logger.warning('Attempt to compare: %s with %s', self, other) raise NotImplementedError return (self.value < other.value) # pylint: disable=C0325,W0143 def __repr__(self): return f'{self.__class__.__name__}: {self}' def __str__(self): return self.name
[docs] @staticmethod def dtype(): return CategoricalDtype(categories=[s.name for s in CompassBearing], ordered=True)
[docs]@functools.total_ordering @enum.unique class Present(Enum): NOT_PRESENT = 0 PRESENT = 1 # noqa: E221
[docs] @staticmethod def parse(value: Any) -> Optional['Present']: if value is None: return None if isinstance(value, str) and value.upper() == 'NA': return None if type(value) == Present: # pylint: disable=C0123 return cast(Present, value) if isinstance(value, bool): return Present.PRESENT if value else Present.NOT_PRESENT value = float(value) if value == 0.0: return Present.NOT_PRESENT if value > 0.0: return Present.PRESENT raise ValueError(f'Failed to parse {Present.__name__}: "{value}"')
def __lt__(self, other): if other is None: return False if isinstance(other, int): other = Present(other) if type(other) != type(self): # pylint: disable=C0123 logger.warning('Attempt to compare: %s with %s', self, other) raise NotImplementedError return (self.value < other.value) # pylint: disable=C0325,W0143 def __repr__(self): return f'{self.__class__.__name__}: {self}' def __str__(self): return self.name
[docs] @staticmethod def dtype(): return CategoricalDtype(categories=[s.name for s in Present], ordered=True)
[docs]@functools.total_ordering @enum.unique class BodyPosition(Enum): SUPINE = 0 # noqa: E221 SUPINE_FLEXED_LEGS = 1 # noqa: E221 CROUCHED = 2 # noqa: E221 CROUCHED_LEFT_SIDE = 3 # noqa: E221 CROUCHED_RIGHT_SIDE = 4 STOMACH = 5 # noqa: E221
[docs] @staticmethod def parse(value: Any) -> Optional['BodyPosition']: if value is None: return None if isinstance(value, str) and value.upper() == 'NA': return None if type(value) == BodyPosition: # pylint: disable=C0123 return cast(BodyPosition, value) if isinstance(value, str): value = value.upper() for position in BodyPosition: if value == position.name: return position if value == position.value: return position raise ValueError(f'Failed to parse {BodyPosition.__name__}: "{value}"')
def __lt__(self, other): if other is None: return False if isinstance(other, int): other = BodyPosition(other) if type(other) != type(self): # pylint: disable=C0123 logger.warning('Attempt to compare: %s with %s', self, other) raise NotImplementedError return (self.value < other.value) # pylint: disable=C0325,W0143 def __repr__(self): return f'{self.__class__.__name__}: {self}' def __str__(self): return self.name
[docs] @staticmethod def dtype(): return CategoricalDtype(categories=[s.name for s in BodyPosition], ordered=True)
KNOWN_GROUPS = { 'utilitarian' : set(['knife', 'whetstone', 'awl', 'scissors', 'vessel', 'pot_sherd', 'flint', 'flakes', 'flint_flakes']), # noqa: E203 'textile' : set(['textile', 'needle', 'spindle_whorl']), # noqa: E203 'equestrian' : set(['equestrian', 'horse_equipment']), # noqa: E203 'economic' : set(['coins', 'scales']), # noqa: E203 'organic_material': set(['animal_remains', 'charcoal', 'flower', 'shell', 'burned_bones']), 'appearance' : set(['brooch', 'cloak ring', 'bracelet', 'beads', 'comb', 'buckle', 'mounts', 'cloak_fastener', 'iron_ring']), # noqa: E203 'burial_container': set(['coffin', 'coffin_substitute']), 'weapons' : set(['sword', 'axe', 'shield_boss', 'spear']), # noqa: E203 'iron_fragment': set(['iron frafments', 'nails', 'iron_object', 'iron_nail/rivets']), 'miscellaneous': set(['lock', 'keys', 'thors_hammer', 'bronze_item', 'bronze_disk', 'quartz', 'unidentified_bronze', 'iron_pole']), }
[docs]class Context(object): """docstring for Context""" def __init__(self, body_position: Optional[BodyPosition], body_orientation: Optional[CompassBearing], disturbed: Optional[Present], decapitation: Optional[Present], double_grave: Optional[Present], stone_layer: Optional[Present], grave_goods: Dict[str, Optional[Any]]): if body_position is not None and not isinstance(body_position, BodyPosition): raise ValueError(f'Invalid body_position: "{body_position}"') self.body_position = body_position if body_orientation is not None and not isinstance(body_orientation, CompassBearing): raise ValueError(f'Invalid body_orientation: "{body_orientation}"') self.body_orientation = body_orientation self.disturbed = disturbed self.decapitation = decapitation self.double_grave = double_grave self.stone_layer = stone_layer self.grave_goods = {k.lower(): Present.parse(v) for k, v in grave_goods.items()} countable_goods = [float(v) for v in grave_goods.values() if Present.parse(v) is not None] # type: ignore self.grave_goods_total = sum(countable_goods) if len(countable_goods) > 0 else None
[docs] @staticmethod def empty(): return Context(None, None, None, None, None, None, {})
[docs] @staticmethod def group(value): value = value.lower() groups = set() for group_name, group in KNOWN_GROUPS.items(): if value in group: groups.add(group_name) return groups
[docs] def to_pd_data_frame(self, index): data = { 'id': pd.Series([index]), } data['body_position_cat'] = pd.Series([self.body_position.name if self.body_position else None], copy=True, dtype=BodyPosition.dtype()) data['body_position_val'] = pd.Series([self.body_position.value if self.body_position else None], copy=True, dtype='Int64') data['body_orientation_cat'] = pd.Series([self.body_orientation.name if self.body_orientation else None], copy=True, dtype=CompassBearing.dtype()) data['body_orientation_val'] = pd.Series([self.body_orientation.value if self.body_orientation else None], copy=True, dtype='Int64') data['disturbed_cat'] = pd.Series([self.disturbed.name if self.disturbed else None], copy=True, dtype=Present.dtype()) data['decapitation_cat'] = pd.Series([self.decapitation.name if self.decapitation else None], copy=True, dtype=Present.dtype()) data['double_grave_cat'] = pd.Series([self.double_grave.name if self.double_grave else None], copy=True, dtype=Present.dtype()) data['stone_layer_cat'] = pd.Series([self.stone_layer.name if self.stone_layer else None], copy=True, dtype=Present.dtype()) for key, value in self.grave_goods.items(): data[f'all_{key}_cat'] = pd.Series([value.name if value else None], copy=True, dtype=Present.dtype()) data[f'all_{key}_val'] = pd.Series([value.value if value else None], copy=True, dtype='Int64') per_group_count = None for group_name, group in KNOWN_GROUPS.items(): status_set = {v for k, v in self.grave_goods.items() if k in group and v} if Present.PRESENT in status_set: present = Present.PRESENT per_group_count = per_group_count + 1 if per_group_count else 1 elif Present.NOT_PRESENT in status_set: present = Present.NOT_PRESENT per_group_count = per_group_count + 0 if per_group_count else 0 else: present = None data[f'{group_name}_cat'] = pd.Series([present.name if present else None], copy=True, dtype=Present.dtype()) data[f'{group_name}_val'] = pd.Series([present.value if present else None], copy=True, dtype='Int64') data['total_grave_goods'] = pd.Series([self.grave_goods_total], copy=True, dtype='Int64') data['total_grave_goods_indicator'] = pd.Series([per_group_count], copy=True, dtype='Int64') return pd.DataFrame.from_dict(data).set_index('id')
if __name__ == "__main__": raise RuntimeError('No main available')