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'