Working with Bow Objects¶
Bow objects are basically containers for the kinds of DataFrames pybow uses to describe bows, plus metadata. They are not necessary to use pybow’s functions, but they can make it more convenient.
Making a Bow Object¶
Initialising a new bow object requires its two mandatory attributes:
name
and source
:
>>> import pybow as pb
>>> my_bow = pb.Bow('My Bow', 'Me')
Set optional metadata attributes:
>>> my_bow.description = 'Just a bow for testing.'
>>> my_bow.material = 'hickory'
To add a limb, make its DataFrame and use the respective function:
>>> import pandas as pd
>>> df1 = pd.DataFrame([[0, 0.9, 0.75],
... [8, 1.58, 0.84],
(...)
... [78, 2.11, 2.45],
... [88, 1.9, 3.3]],
... columns=['l', 'width', 'thickness'])
>>> len(my_bow) # len() returns a bow object’s number of limbs
0
>>> my_bow.add_limb(df1)
>>> len(my_bow)
1
Alternatively, all attributes can be set directly at initialisation:
>>> my_other_bow = pb.Bow('My Other Bow', 'Me',
... description='Another Bow for testing',
... material='hickory'
... limbs=[df1])
>>> len(my_other_bow)
1
>>> my_other_bow.material
'hickory'
Querying the Bow Object¶
Bow objects have a few callable properties derived from its limb measurements:
>>> # Junkmanns’ measurements for the Rotten Bottom artefact
>>> rb = pb.data.junkmanns2013_rottenbottom
>>> rb.abslength # absolute length encoded in the DataFrames, in cm
113
>>> rb.ntnlength # nock-to-nock length
nan
>>> # ntnlength is only a number if a bow has two complete limbs
As a convenience, each limb’s material
property returns its parent bow’s material
attribute:
>>> rb.material
'yew'
>>> rb[0].material
'yew'
Bow objects yield their limbs by subscripting, like lists, or by iterating over them.
Limb DataFrames are stored in a limb’s data
attribute.
Subscripting:
>>> rb[0].data # DataFrame for the first limb
l width thickness
0 0 0.90 0.75
1 8 1.58 0.84
(...)
9 78 2.11 2.45
10 88 1.90 3.30
Iterating:
>>> for limb in rb:
... print(limb.data)
l width thickness
0 0 0.90 0.75
1 8 1.58 0.84
(...)
9 78 2.11 2.45
10 88 1.90 3.30
l width thickness
0 0 2.77 1.51
1 5 2.72 1.65
2 15 2.27 2.32
3 25 1.90 3.30
Iterating lends itself for filtering suitable bow limbs from a collection.
Consider a limb’s boolean complete
argument, denoting whether a DataFrame describes a complete limb (as opposed to a broken or not sufficiently preserved limb):
>>> rb[0].complete
True
>>> rb[1].complete
False
>>> [limb.data for limb in bow if limb.complete]
l width thickness
0 0 0.90 0.75
1 8 1.58 0.84
(...)
9 78 2.11 2.45
10 88 1.90 3.30
If you have a corpus of bow objects, you can leverage Python’s nested list comprehensions to generate a filtered list of limbs to feed into a function:
>>> my_bow_corpus = [pb.data.junkmanns2013_rottenbottom,
... pb.data.beckhoff1964_vrees]
>>> limbs = [limb.data
... for bow in my_bow_corpus for limb in bow
... if limb.complete]
>>> print(*limbs, sep='\n') # rb’s limb 1 & vrees’ only limb
l width thickness
0 0 0.90 0.75
1 8 1.58 0.84
(...)
l width thickness I e_belly
0 14.0 2.2 1.40 0.36 0.60
1 19.3 3.0 1.55 0.59 0.65
(...)
Note
If the order of Python’s nested list comprehension syntax confuses you, see PEP 202#the-proposed-solution for the reasoning behind it. (I am so not going to judge you.)
Running an Analysis from a Bow Object¶
For feeding into an analysis function, the object of choice is the limb object:
Its data
attribute gives you the DataFrame of measurements and its material
property gives you the containing bow’s material
attribute.
This is a very explicit version of the tutorial’s analysis, highlighting how the bow object’s metadata allows you to easily run analysis functions with automated input:
>>> rb[0].data
l width thickness
0 0 0.90 0.75
1 8 1.58 0.84
(...)
9 78 2.11 2.45
10 88 1.90 3.30
>>> rb[0].material
'yew'
>>> data_dict = pb.data.beckhoff1964
>>> data_dict
{'yew': {'MoE': 11770000000.0, 'MoR': 61290000.0}}
>>> pb.analysis.beckhoff1964(rb[0].data,
... data_dict[rb[0].material]['MoE'],
... data_dict[rb[0].material]['MoR'])
(drawweight 101.297062
drawlength 84.031281
braceheight 18.907038
ntnlength 176.000000
set 7.040000
energy 32.860779
dtype: float64,
l width thickness e_belly I ...
0 0 0.90 0.75 0.318310 0.020837 ...
(...)
10 88 1.90 3.30 1.400563 3.747119 ...)
The bow objects’ potential for automating analyses comes to bear most when working with a corpus of bows:
>>> import pandas as pd
>>> candidates = [limb
... for bow in my_bow_corpus for limb in bow
... if limb.complete]
>>> results = []
>>> for l in candidates:
... material = data_dict[l.material]
... series, df = pd.analysis.beckhoff1964(l.data,
... material['MoE'],
... material['MoR'],
... name=l.name)
... results.append(series)
>>> results_df = pd.DataFrame(results)
>>> results_df
drawweight drawlength ...
Rotten Bottom_0 101.297062 84.031281 ...
Vrees_0 194.779434 54.952892 ...
Writing to and Reading from Hard Drive¶
To write a bow object to disk, use its write()
method:
>>> rb.write('my_bow.yaml')
This will write three files:
my_bow.yaml
with the bow and limb metadata, my_bow_0.csv
, and my_bow_1.csv
with the measurements for the limbs.
To read your bow object again, use the read()
function with the bow’s YAML file’s name:
>>> rb2 = pb.read('my_bow.yaml')
>>> rb2.name
'Rotten Bottom'