Skip to content

Commit

Permalink
Finalize raw_input().
Browse files Browse the repository at this point in the history
  • Loading branch information
theonaunheim committed Sep 15, 2019
1 parent 107a58e commit a11d96d
Show file tree
Hide file tree
Showing 8 changed files with 1,520 additions and 17 deletions.
12 changes: 6 additions & 6 deletions RELEASE_NOTES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@ Features
Version 0.1-alpha.1
-------------------

Enhancements
~~~~~~~~~~~~

* Additional documentation for items in `report` and `utility` modules.

Version 0.1-alpha.2
Expand All @@ -33,7 +30,10 @@ Version 0.1-alpha.3
Version 0.1-alpha.4
-------------------

Fixes
~~~~~
* Correct inappropriate Vulnerability calculation.

Version 0.1-alpha.5
-------------------

* Correct inappropriate Vulnerability calculation.
* Added raw_input() function and associated storage routines.
* Improved PEP8 compliance.
59 changes: 58 additions & 1 deletion pyfair/model/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class FairModel(object):
##########################################################################
# Creation Methods
##########################################################################

def __init__(self,
name,
n_simulations=10_000,
Expand Down Expand Up @@ -167,8 +167,14 @@ def read_json(param_json):
for param_name, param_value in data.items():
# If it's not in the drop list, load it.
if param_name not in drop_params:
# Input params must be treated differently if raw
contains_raw = 'raw' in param_value.keys()
if param_name.startswith('multi'):
model.input_multi_data(param_name, param_value)
# Raw needs a different way
elif contains_raw:
model.input_raw_data(param_name, param_value['raw'])
# Otherwise standard input
else:
model.input_data(param_name, **param_value)
# Calculate
Expand Down Expand Up @@ -354,6 +360,57 @@ def bulk_import_data(self, param_dictionary):
self.input_data(target, **parameters)
return self

def input_raw_data(self, target, array):
"""Supply a raw array to the model
This function allows for arbitrary data to be placed within the
model by passing an array.
Parameters
----------
target : str
The node to which to send the data
array : np.array, list, or pd.Series
Raw input data for the model
Returns
-------
pyfair.model.FairModel
A reference to this object of type FairModel
Raises
------
FairException
If an inappropriate number of items are supplied
Examples
--------
>>> array = np.array([1,2,3])
>>> model = pyfair.FairModel(name="Insider Threat")
>>> model.input_raw_data('Loss Magnitude', array)
"""
# Standardize inputs to account for abbreviations
target = self._standardize_target(target)
# Check types
acceptable_types = [list, np.array, pd.Series]
if type(array) not in acceptable_types:
raise FairException('Inappropriate input type.')
# Check length
if len(array) != self._n_simulations:
message = "Input legnth {}, but simulations count is is {}".format(
len(array),
self._n_simulations
)
raise FairException(message)
# Generate data via data captive class
data = self._data_input.supply_raw(target, array)
# Update dependency tracker captive class
self._tree.update_status(target, 'Supplied')
# Update the model table with the generated data
self._model_table[target] = data
return self

def _standardize_target(self, target):
"""A function to change target abbreviations into full names"""
if target in self._target_map.keys():
Expand Down
41 changes: 41 additions & 0 deletions pyfair/model/model_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,47 @@ def generate_multi(self, prefixed_target, count, kwargs_dict):
self._supplied_values[new_target] = kwargs_dict
return summed

def supply_raw(self, target, array):
"""Supply raw data to the model
This takes an arbitrary array, runs some quick checks, and returns
the array if appropriate.
Parameters
----------
target : str
The eventual target of the raw data
array : list, pd.Series, or array
The raw data being supplied
Returns
=======
np.array
The data for the model
Raises
------
pyfair.utility.fair_exception.FairException
Raised if the data has null values
"""
# Ensure numeric
clean_array = pd.to_numeric(array)
# Coerce to series
if type(array) == pd.Series:
s = pd.Series(clean_array.values)
else:
s = pd.Series(clean_array)
# Check numeric and not null
if s.isnull().any():
raise FairException('Supplied data contains null values')
# Ensure values are appropriate
if target in self._le_1_targets:
if s.max() > 1 or s.min() < 0:
raise FairException(f'{target} data greater or less than one')
self._supplied_values[target] = {'raw': s.values.tolist()}
return s.values

def _determine_func(self, **kwargs):
"""Checks keywords and returns the appropriate function object."""
# Check whether keys are recognized
Expand Down
1 change: 1 addition & 0 deletions pyfair/report/base_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,7 @@ def _get_model_parameter_table(self, model):
# On a column basis
axis=1,
)
param_df = param_df.applymap(lambda x: '' if 'nan' in x else x)
# Do not truncate our base64 images.
pd.set_option('display.max_colwidth', -1)
# Create our distribution icons as strings in table
Expand Down
35 changes: 26 additions & 9 deletions pyfair/report/tree_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ def __init__(self, model, format_strings):
self._params = pd.DataFrame(model.export_params()).T.reindex(self._statuses.index)
# Tack all data together
self._data = pd.concat([
self._statuses,
self._DIMENSIONS,
self._statuses,
self._DIMENSIONS,
self._result_summary,
self._params
], axis=1, sort=True)
Expand Down Expand Up @@ -124,6 +124,14 @@ def _generate_text(self, row, ax):
# Set conditions
calculated = row['status'] == 'Calculated'
supplied = row['status'] == 'Supplied'
# Raw inputs will have a list
if 'raw' in row.index:
if type(row['raw']) == list:
raw = True
else:
raw = False
else:
raw = False
if calculated:
# Get rid of items with value
data = row.loc[['μ', 'σ', '↑']].dropna()
Expand All @@ -145,12 +153,21 @@ def _generate_text(self, row, ax):
data = data.map(lambda x: fmt.format(x))
# Get max length of stirng
value_just = data.str.len().max()
# Output format
output = '\n'.join([
key + ' ' + value.rjust(value_just)
for key, value
in data.iteritems()
])
# Output format for raw
if raw:
output = '\n'.join([
key + ' ' + value.rjust(value_just)
for key, value
in data.iteritems()
])
output = 'Raw input'
# And verything else ... so much nesting
else:
output = '\n'.join([
key + ' ' + value.rjust(value_just)
for key, value
in data.iteritems()
])
else:
output = ''
plt.text(
Expand Down Expand Up @@ -201,7 +218,7 @@ def generate_image(self):
"""
fig, ax = plt.subplots()
fig.set_size_inches(20,6)
fig.set_size_inches(20, 6)
ax = self._tweak_axes(ax)
self._data.apply(self._generate_text, args=[ax], axis=1)
self._generate_rects(ax)
Expand Down
1,368 changes: 1,368 additions & 0 deletions pyfair_scratch.ipynb

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions tests/model/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import pathlib
import unittest

import numpy as np
import pandas as pd

from pyfair.model.model import FairModel
Expand Down Expand Up @@ -77,6 +78,24 @@ def test_inputs(self):
'Secondary Loss Event Magnitude': {'low': 10, 'mode': 20, 'high': 100},
}
})
# Test input_raw_data
model.input_raw_data(
'Vulnerability',
[1] * self.N_SAMPLES
)
self.assertRaises(
FairException,
model.input_raw_data,
'Vulnerability',
[2] * self.N_SAMPLES
)
self.assertRaises(
FairException,
model.input_raw_data,
'Vulnerability',
'abc'
)
model.calculate_all()

def test_calculation(self):
"""Run a calulate all."""
Expand Down
2 changes: 1 addition & 1 deletion tests/report/test_tree_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def test_tree_graph_creation(self):
with warnings.catch_warnings(record=False):
warnings.simplefilter("ignore")
ftg = FairTreeGraph(model, self._FORMAT_STRINGS)
_, _ = ftg.generate_image()
_, _ = ftg.generate_image()


if __name__ == '__main__':
Expand Down

0 comments on commit a11d96d

Please sign in to comment.