The Bow Object as a Glorified DataFrame Container

The Bow class is not much more than a tool to spit out the DataFrames that describe a bow artefact, plus some metadata. This is deliberate.

In part, this stems from the Junkmanns and Beckhoff implementations that were the initial seeds for starting this package. They were done for a Digital Humanities seminar at the University of Würzburg, and thus very bare-bones, taking plain DataFrames as artefact measurement input. Once I decided to build a package from this code base, and let it have a dedicated class for representing bows, there were two options to design it:

Option 1 was to leave the methods largely unchanged, and make the Bow class be a supplier of DataFrames to feed into the Beckhoff and Junkmanns analysis functions. It would look something like this:

>>> my_bow
<pb.Bow …>
>>> pb.analysis_function(my_bow[0].data,
                         material_value,
                         another_material_value,
                         some_switch=False)
results

Option 2 was to change the analysis functions to take Bow classes as input and have them pull all required metadata from the supplied class instead of separate function arguments. It would look something like this:

>>> my_bow
<pb.Bow …>
>>> pb.analysis_function(my_bow,
                         material_values_container,
                         some_switch=False)
results

While on the face of it, option 2 looks easier to handle, with a less cluttered function signature, I believe option 1 to be the better solution.

The cost of option 2 being able to accept an entire Bow object as input is that the function must have a lot of knowledge about how the Bow object stores its data and metadata. If the Bow class’ metadata is to be used to determine things like correct material data values, the function must be given a container object holding a catalogue of values for different materials, like a dict, structured in a specific way. The function must also have knowledge of how these catalogue objects are structured, and the supplied catalogue must be structured correctly.

Also, it is impossible to see which data and metadata actually factors into an analysis without looking at the function’s source code. Therefore, this apparent simplification really is an indirection that comes with the nontrivial cost for the user to make several complex objects, even for the smallest and most exploratory undertakings.

Note

Going fully Object-Oriented and making the analysis functions be methods of the Bow class, like:

my_bow.analysis_method(material_values_container,
                       some_switch=False)

would just take the indirection a half-step further without offering any benefits over either option 1 or 2 I’m aware of.

Option 1, on the other hand, looks more cluttered initially, but it is much more explicit: An analysis function requiring a material’s modulus of elasticity and modulus of rupture will have it written right into its signature. Whether you supply these values from a dict or manually is up to what is more convenient for you. If all you want to do is look at how taking off 2 millimeters of thickness will change a planned bow’s draw weight or bending behavior, you don’t need to build a complete Bow object – just modify a DataFrame and feed it into the analysis function.

Analysis functions in option 1 require much less knowledge about external data structures as well: All their input is the DataFrame and material data values. Where they come from is not the concern of the analysis function.

Therefore, the Bow class’ main task in option 1 becomes containing and supplying Dataframes and metadata. I much prefer this option,

  1. because it improves how much knowledge about external data structures the analysis functions need to have,
  2. because it consequently is more explicit about what is an analysis’ actual input,
  3. and because it doesn’t prescribe one way of doing things.

If you want to play around exploratively with some measurements, make a DataFrame and throw it into your analysis function of choice, along with some hardcoded material data values. If you have a sizeable collection of bow data and want a convenient way of storing it for bulk-applying analysis functions to it, make Bow objects. If you already have bow (meta)data stored your own way, go ahead and use it – as long as you can load it into appropriately sized/formatted DataFrames, you’re good.

I hope this sufficiently explains my motivation for introducing a custom class that, at the end of the day, doesn’t really offer much in terms of spectacular functionality.