Priority Management with Python

With so many different things to do these days, determining how much time to spend on each one can be hard. Especially when all the choices are things I want to pursue, balancing competing interests so I still grow in all of them is a hard problem. How do we solve hard problems? Throw them at computers!

First, I had to make a model which described my different interests and how I prioritize them. I decided to make the equivalent of a “mind map”, a graph with one central node that decays into its conceptual components. This graph has one root node, and its weight is equal to the amount of time I’m distributing. This might be a day, week, month, or any other duration. This might seem a little unintuitive, but I think an example will make it more clear:

I want to take care of my body. I decided to spend one sixth of my time making sure I did! Now, what goes into that? Staying fed and hydrated, getting exercise, and (in my case) following a night time ritual to make sure I get some sleep. Now, how much time should go to each of those? Setting aside time to stay fed is pretty huge, so I gave it 3/7 of my “Body” time. Exercise doesn’t happen every day, so it gets a little less: 2/7. Within exercise, I want to improve my cardiovascular fitness as well as build up some muscle. Let’s set 1/2 of my exercise time for the former and 1/2 for the latter. That leaves 1/7 of my time to divide evenly between personal hygiene and night time ritual.

With all of those values set, I could plug any duration of time into the script and find out precisely how much of that time I should spend on each of those activities. Now that the model has been motivated, let’s talk about the actual code. I drew out my priorities as a DAG where each node represented one activity or aspect of myself that needed time and attention. Each node, or Activity, decomposes into its component aspects or categories. Additionally, all of the edges going out of a node have weights which sum to 1. The class initially looks like this:


class Activity(object):
    def __init__(self, name, weight_num, weight_denom):
        self.name = name
        self.weight = Fraction(weight_num, weight_denom)
        self.subactivities = {}
        self.parent = None

    def add_sub(self, new_activity):
        # Run a check to make sure all the weights supplied haven't
        # yet summed to a value greater than 1.  If it passes, add 
        # the new Activity to the current Activity's subactivity 
        # dictionary and set the new Activity's parent equal to the
        # current Activity.
        weight_sum = sum([activity.weight for activity in self.subactivities.values()])
        weight_sum += new_activity.weight
        if weight_sum > 1:
            raise Exception("The weight of the subdivisions of %s\
                are too great to add in %s with a weight of %s!"\
                %(self.name,new_activity.name, new_activity.weight))
        else:
            self.subactivities[new_activity.name] = new_activity
            new_activity.parent = self

The script then uses the node edge weights to recursively divide the amount of time out among each activity. Each Activity prints out the amount of time which should go towards it and then calls each of the child activities with their appropriate amounts of time.


    def print_time_allotment(self, time, depth=0):
        # Add in some tabbing to visual structure in the output
        print "----"*depth,

        # Print time spent
        print "Spending %s on %s." % (str(time), self.name)
        
        # If the activity has subactivities, check that their weights
        # sum to 1.  Then calculate how much time each will get based
        # on this Activity's weight and each subactivity's priority 
        # fraction.
        if self.subactivities != {}:
            weight_sum = sum([activity.weight for activity in self.subactivities.values()])
            if weight_sum != 1:
                raise Exception("The weights of the subactivities don't sum to 1.")
            for activity in self.subactivities.values():
                newtime = time * activity.weight.numerator
                newtime = newtime / activity.weight.denominator
                activity.print_time_allotment(newtime, depth+1)
            print ""

To test the script, I made my own priority set and ran it with a 16-hour day (apparently some people get 8 hours of sleep). When you run it all together, you end up with an output that looks something like this:

 Spending 16:00:00 on Self.
---- Spending 2:40:00 on Body.
-------- Spending 0:22:51.428571 on Good Hygiene.
-------- Spending 1:08:34.285714 on Eat 3 Meals/Day.
-------- Spending 0:45:42.857142 on Consistent Exercise.
------------ Spending 0:22:51.428571 on Muscle Building.
------------ Spending 0:22:51.428571 on Aerobic Conditioning.

-------- Spending 0:11:25.714285 on Nighttime Ritual.
-------- Spending 0:11:25.714285 on Keep Hydrated.

---- Spending 10:40:00 on Mind.
-------- Spending 1:20:00 on Entrepreneurial.
------------ Spending 0:20:00 on Groupify Development.
------------ Spending 0:20:00 on Visualization Project.
------------ Spending 0:20:00 on Leadership Practice.

-------- Spending 8:00:00 on Code & Craft.
------------ Spending 6:00:00 on Work at Cogo.
------------ Spending 1:00:00 on Practice with Ruby on Rails.
------------ Spending 1:00:00 on Study Linear Algebra.

-------- Spending 1:20:00 on Artistic.
------------ Spending 0:20:00 on Work on game.
------------ Spending 0:20:00 on Write blog posts.
------------ Spending 0:20:00 on Play the drums.
------------ Spending 0:20:00 on Practice drafting.


---- Spending 2:40:00 on Spirit.
-------- Spending 0:26:40 on Keep space clean.
-------- Spending 0:40:00 on Meditation & Introvert Time.
-------- Spending 0:40:00 on Catch up with family.
-------- Spending 0:53:20 on Spend time with friends.

I later added in a helper function to read in data from .csv file. It assumes a .csv with a header row whose entries are the: Activity Name, Activity Parent, Activity Weight Numerator, and Activity Weight Denominator. It doesn’t need those precise heading names, but it does assume those are the values. You can give that a read right here:


def parse_csv_to_activities(filename):
    # Need to open a csv where the column structure is:
    # name, parent, numerator, denominator
    # Note that we use Fractions instead of Float arithmetic.
    activity_list = []
    total_time = Activity("Self", 1, 1)
    activity_list.append(total_time)

    with open(filename, 'rb') as csvfile:
        # Make the reader, skip a line to get past the header row.
        activity_reader = csv.reader(csvfile)
        activity_reader.next()

        # For each activity in the table:
        #   If the specified parent exists, then create the new
        #   activity and add it as a sub-activity to the parent.
        for row in activity_reader:
            activity_names = [activity.name for activity in activity_list]
            (name, parent, numerator, denominator) = row
            if parent in activity_names:
                new_activity = Activity(name, int(numerator), int(denominator))
                parent_index = activity_names.index(parent)
                parent = activity_list[parent_index]
                parent.add_sub(new_activity)
                activity_list.append(new_activity)
    return total_time

I’ve included a link to the .csv file I made so that you have an example to go off of if you want to try it out. I could imagine expansions to the project which visualize the priorities in a directed graph, or a web interface to let users make their own. For now, though, this is as far as it’s being developed. With that said, I hope some people find this useful or interesting. Thanks for reading!

python code
.csv data

About these ads

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s