The latest version can be downloaded on the App Store.
This synopsis focuses only on the app development, to learn about the data science / algorithmically interesting side of this project I continued to work on since the publication of the app, read here.
Backstory
The backstory is the same as that of aiRoute, except I wanted to do a similar thing for food choice. Similar to which route to run on, I thought of food as a similar optimization game of nutrition and enjoyment (in this scenario, the former much more so than the latter).
Gathering Ingredients
I first needed a high quality database of ingredients from which I can build meals from. Most databases, like that of Nutritionix, are costly, and open-sourced ones, like USDA’s, were too scattered. Commercial ones, like Costco and Whole Foods, have obvious commercial reasons to not open-sourcing their offerings list. There was also the option of directly scraping from recipe websites, but I heard from a friend allrecipes blocks those who excessively scrape from the site, and as a then-novice-web-scraper I didn’t want to go through the trouble of aggregating numerous sources with different html DOM formats. I did try to scrape from Bodybuilders.com, but beautifulsoup4 (Python web scraping library) didn’t find some of the space formatting on the webpages too friendly.
Formulation
I envisioned the MVP to have the following features:
- A random meal generation algorithm to display in TableView form a list of ingredients that precisely satisfy the four major macronutrient categories (calories, protein, fat, carbs)
- A meal plan algorithm that can partition the list into balanced, tasty meals
- User authentication, persistence, save lists, good UX, nutritional needs calculation, etc.
Tech Stack
Nutritionix – API used to fetch macro/micro nutritional information for an ingredient
Although I mainly used this API for the data science side of the project, I decided to incorporate it into my app as a small feature (explained more below) to serve as human-in-the-loop.
Firebase – This was my pre-Django days, and I used NoSQL for aiRoute, so I decided to stick to it.
Meal generation algorithm
Because the size of the cleaned database (see here for more details) isn’t large, I stuck with a more brute force method (for good purpose) by randomly generating ingredients until its caloric count exceeded that of the target, then repeatedly iteratively replacing one ingredient in the list by another not in the list such that the overall error|calories_list – calories_target| + |protein_list – protein_target| + |fat_list – fat_target| + |carbs_list – carbs_target| is reduced by the largest margin. This is done a pre-fixed amount of times unless convergence is reached.
Improvements on this algorithm and much more is talked about in MealApp.
Meal plan algorithm
To cluster a set of ingredients into distinct, I applied the heuristic:
The meal compatibility of two ingredients could be solely determined by the number of meals for which these two ingredients appear together.
That heuristic leads naturally to spectral clustering. From it, I derived the affinity matrix, and from that the Laplacian, followed by an eigenvalue search using scipy package so I can use sklearn’s K-means implementation to finish.
My inner-cook must’ve been on substance when I convinced myself that, because that assumption is far from sufficient. See MealApp (under modeling) for my explanation on why and an improvement to this method.
Features
Using the Nutritionix API, I enabled users to add ingredients by search, but only once the clustered meal plan has been generated. I made it only a minor feature because I wanted to automate as much of the meal generation and clustering as I could, so this human-in-the-loop action allows me to later analyze what the meal is missing and add personalization. In addition, the API limits 200 calls per account on free tier.
Moreover, I added logging to all user interactions with the generated list (including dragging an ingredient from one meal to another), which can be used for updating the affinity or more advanced personalization later.
Development in Swift
The UX development was very challenging but rewarding; I worked with TableView, CollectionView, and ScrollView for the first time. The features I ended up implementing:
- Popover introduction
- Gesture recognition
I implemented drag/drop with the Apple Developer tutorial.
- Meal interaction logging
This was fun to implement. Every session of dragging/dropping an ingredient from one meal to another is logged in Firebase. Then, the pairwise ingredient affinity matrix is updated on the basis of which ingredients are present of the from:meal and which are present of the to:meal. Testing this out, I realize the cause of moving an ingredient is usually because it is compatible with a specific ingredient in the to:list (i.e. blueberries to […, oatmeal, …]). Now this can be modeled nicely with Bayesian inference and conditional probabilities; hopefully I’ll get around to coding this up soon…