Message Boards Message Boards

Using Wolfram to Take the Debate out of our Weekly Lunch

GROUPS:

Huge shout-out to Paul Winterbotham for being the creative mind that gets all of these projects started. He regularly comes to me with ideas of fun one-off projects that help me create usable content like this.

Some background on the project: a few of us here at Wolfram regularly plan a Friday lunch. Each week, we either force one of our colleagues to just pick a spot or have this long debate over where we should go. Instead of continuing to have this drawn out discussion, we decided to have everyone commit about 30 seconds to filling out a form, so we could let Mathematica decide for us! I thought it was a fun use case of the tech and a nice way to flex my own brain muscles to optimize the idea into something easily usable each week.

Databin Creation

To get started, it was necessary to create a databin using the CreateDatabin function to store all of our responses to the survey each week. I'll usually only do this once per notebook, as to not create too many empty databins in my Wolfram Data Drop. I'll then just copy and paste that output into a variable, so I'm not recreating databins every time I open the notebook.

CreateDatabin[]

enter image description here

FormPage Creation

For those that have read some of my previous posts, you probably remember the standard template I've given for a FormPage. This is obviously an oversimplification of the code but is certainly a good way to start an outline of what you'll need to include.

FormPage[
 "inputs",
 "action items",
 "appearance rules"
 ]

For our FormPage, we simply wanted to collect each person's name and top 4 ranked choices for lunch. An additional piece of data CurrentDate["Week"] is also added to the databin using the DatabinAdd in the "action items" section of the FormPage function in order to help sort the inputs later. Within the notebook, you can see what this page will look like before deploying it to the Wolfram Cloud.

fp = FormPage[{
   {"name", "Your Name:"} -> "String",
   {"firstchoice", "1st Choice:"} -> "String",
   {"secondchoice", "2nd Choice:"} -> "String",
   {"thirdchoice", "3rd Choice:"} -> "String",
   {"fourthchoice", "4th Choice:"} -> "String"},
  (DatabinAdd[
     db, {#name, #firstchoice, #secondchoice, #thirdchoice, #fourthchoice, CurrentDate["Week"]}];
    Rasterize[
     Style["Thanks for participating; your suggestions will be taken into consideration", Medium, Black]]) &,
  AppearanceRules -> <|
    "Title" -> "Lunch Location Survey",
    "Description" -> 
     "Please fill out survey to request restaurants for lunch"
    |>
  ]

enter image description here

For convenience purposes, it's ideal to deploy this to the Wolfram Cloud using the CloudDeploy functionality. We are able to give our form a unique URL with the tag "LunchSelectionForm" using this function. By setting the Permissions to "Public", all of our coworkers are able to access without logging in. You can also access the form at this link. A very simple example of how the Wolfram Language saves you a lot of time in creating and deploying data-collecting forms.

CloudDeploy[fp, "LunchSelectionForm", Permissions -> "Public"]

Data Aggregation and Analysis

The next step in this analysis was to group the collected data by the week in which it was collected. This is where that final DatabinAdd value comes in handy. Since it is the last added value, it's very simple to just do a GroupBy function using Values to pull the data from the databin and Last to actually group them by that last input date value.

data = GroupBy[Values[db], Last]

enter image description here

For our purposes, we only care about one week of data at a time, specifically the current week. If you understand Keys in Wolfram Language this is really easily pulled. Our key of interest is the CurrentDate["Week"], so we are able to easily pull this from data using that key.

thisWks = data[CurrentDate["Week"]]

enter image description here

Our next step was to create a weighted list of 10 random selections from each coworker's ranked list of restaurants. This allows for some reliance on each person's ranking but gives an additional layer of randomness to the final selection. This list of 10 was simply done to give each person an equal stake in the final list of restaurants to be used for the final randomizer.

selection = 
 Flatten[RandomChoice[{0.4, 0.3, 0.2, 0.1} -> {thisWks[[#]][[2]], 
       thisWks[[#]][[3]], thisWks[[#]][[4]], thisWks[[#]][[5]]}, {10, 
      1}] & /@ Range[Length[thisWks]]]

enter image description here

As mentioned, we considered stopping here and deciding where to go based on everyone's weighted responses. However, it seemed a bit more fun to use this full weighted list with a final randomizer to make it seem more up to fate. This small sample size did however show some initial errors with the way we were using the Databin values. You'll notice that we only had 4 users input, but that we were getting way more that 40 values in our BarChart. This suggests that someone's selections may be getting more weight than those of the others.

cts = Counts[selection];
BarChart[cts, BarOrigin -> Left, 
 ChartLabels -> Placed[{Values[cts], Keys[cts]}, {After, Before}]]

enter image description here

Data Errors

Multiple Submissions per User

You'll notice that our friend, Paul W, has submitted the form multiple times this week. This is the reasoning behind why we were receiving far more than 40 inputs in our BarChart. To eliminate this issue we can add another GroupBy to sort by user and simply pull each user's last submitted list for the random pool.

Using the same idea as before, we specifically use the Key that relates to this week's data to pull the submissions from this week. However, we take it a step further by using GroupBy and First to create a list of each submitter's responses.

groupedWkData = GroupBy[data[CurrentDate["Week"]], First]

enter image description here

We can then use the Last and Slot function to iterate through each individual and pull their last recorded responses. This seemed like the most fair way to select which response to use from each person and actually made it easier to allow for resubmission if someone changed their mind.

lastResp = Last[groupedWkData[[#]]] & /@ Range[Length[groupedWkData]]

enter image description here

The new generated BarChart only shows 40 random selections form the generated lists, as expected when giving 10 inputs for the 4 coworkers that submitted.

adjustSelect = 
  Flatten[RandomChoice[{0.4, 0.3, 0.2, 0.1} -> {lastResp[[#]][[2]], 
        lastResp[[#]][[3]], lastResp[[#]][[4]], 
        lastResp[[#]][[5]]}, {10, 1}] & /@ Range[Length[lastResp]]];
cts = Counts[adjustSelect];
BarChart[cts, BarOrigin -> Left, 
 ChartLabels -> Placed[{Values[cts], Keys[cts]}, {After, Before}]]

enter image description here

I will note that we hit a little snag in using this last week, when one of our coworkers decided to be cheeky with the names he input. Users that give multiple nicknames for different submissions can get by this grouping option. We've considered simply adding a list of coworkers similar to the solution provided in the next section for the restaurants to avoid this issue as well.

Spelling Errors in Restaurant Input

In our example, you'll notice that BDubs and BWW were both submitted. This is likely supposed to be the same restaurant, Buffalo Wild Wings, but as with any form, we are subject to human error in input. It may be more ideal to setup a list of restaurants for users to submit to avoid this type of issue. For purposes of not having to add new restaurants down the road, we simply avoided this and relied on our own intuition to see if there was duplicates with different names. However, it'd be a pretty simple fix, as you can see below.

For this, we worked together to discuss some of the places in town that we frequent and a few places that fit into our normal lunch-type restaurants. This is what we came up with. There's probably some better ways to pull this data, but we wanted to minimize the list to what everyone in our group would be interested in rather than adding a bunch of restaurants we would never go to. A bit manual, but worth the customization!

restaurants = {"Baxter's American Grille", "Big Grove Tavern", 
   "Billy Barooz Pub & Grill", "Black Dog Smoke & Ale House", 
   "Blaze Pizza", "Buffalo Wild Wings", "Cactus Grill", 
   "Core Life Eatery", "Courier Cafe", "Cracked", "Crane Alley", 
   "DP Dough", "Dancing Dog Eatery & Juicery", "Destihl Brew Works", 
   "Dos Reales", "Dragon Fire", "El Toro", "Farren's Pub & Eatery", 
   "Fiesta Cafe", "Firehaus", "Five Guys Burger & Fries", 
   "Giordano's Pizza", "Golden Harbor", "Guido's Bar & Grill", 
   "Houlihan's", "HuHot Mongolian Grill", "Joe's Brewery", 
   "Jupiter's Pizzeria & Billiards", "Ko Fusion", "Le Peep", 
   "Legends Bar & Grill", "Lil Porgy's BBQ", "Maize Mexican Grill", 
   "Manolo's Pizza & Empanadas", "Manzella's Italian Patio", 
   "Mas Amigos", "Meatheads Burgers & Fries", "Mia Za's", "Miga", 
   "Moe's Southwest Grill", "Monical's", "Murphy's Pub", 
   "Old Chicago Pizza", "Original Pancake House", 
   "Panchero's Mexican Grill", "Papa Del's", 
   "Perkin's Restaurant & Bakery", "Pizzeria Antica", "Potbelly's", 
   "Radio Maria", "Rainbow Garden", "Red Robin Gourmet Burgers", 
   "Salad Meister", "Scotty's Brewhouse", "Seven Saints", 
   "Sun Singer Wine & Spirits", "Thara Thai", "That Burger Joint", 
   "The Hub Champaign", "Watson's Shack & Rail", "Wendy's", "Zorbas"};

To adjust the code, we simply had to change the input accepted for the choices. You'll notice everything in the code is the same, we just added a restaurants variable in the place of "String" after the arrows. This imports our list of restaurants into a drop-down vs. the free-form input that was used previously. I've deployed this to a separate Cloud Form, which you can view here.

CloudDeploy[FormPage[{
   {"name", "Your Name:"} -> "String",
   {"firstchoice", "1st Choice:"} -> restaurants,
   {"secondchoice", "2nd Choice:"} -> restaurants,
   {"thirdchoice", "3rd Choice:"} -> restaurants,
   {"fourthchoice", "4th Choice:"} -> restaurants},
  (DatabinAdd[
     db, {#name, #firstchoice, #secondchoice, #thirdchoice, #fourthchoice, CurrentDate["Week"]}];
    Rasterize[
     Style["Thanks for participating; your suggestions will be taken into consideration", Medium, Black]]) &,
  AppearanceRules -> <|
    "Title" -> "Lunch Location Survey",
    "Description" -> 
     "Please fill out survey to request restaurants for lunch"
    |>
  ], "LunchDropDownForm", Permissions -> "Public"]

RandomChoice from the Weighted List

The final step was simply to randomly select from our generated weighted list 1000 times and leave our lunch selection up to fate! It does slightly skew the final output to some of our most preferred restaurants per that initial weighting of the selections but does still use that randomization to vary the winner. Since we used the initial form, there's obviously some manual addition that has to be done for the BWW/BDubs dilemma, but otherwise the BarChart provides us with an obvious choice for our weekly lunch!

randomFull = RandomChoice[adjustSelect, 1000];
fullCt = Counts[randomFull];
BarChart[fullCt, BarOrigin -> Left, 
 ChartLabels -> 
  Placed[{Values[fullCt], Keys[fullCt]}, {After, Before}]] 

enter image description here

Conclusion

Yes, this may seem like a simple and silly one-off project. However, it does showcase a quick and simple everyday use-case for the language. The initial code for this was created in 10 minutes by my coworker Paul, someone who before starting here did not have coding experience. With a few short lines of code, he was able to quickly create a web form that collected data as well as both of the randomizers for the data. I simply added in some of the error checking and bar charts for a friendly user interface.

Knowing that the language is geared towards making everyday use-cases like this simple to program definitely gets your mind flowing on other applications that are also easily scaled to full-scale commercial solutions. We use a lot of the free capabilities of Data Drop and Wolfram Cloud, but those certainly could be replaced for full-scale customer surveys from your own site's forms and databases.

Programming in the Wolfram Language is not just for the experienced data analyst or developer. With such a simple interface and built-in functions to make the code more intuitive, it's easy for everyone from a salesperson to a data analyst to a teacher to pick up and start using for their own analytics.

Attachments:
POSTED BY: Sam Tone
Answer
4 days ago

Group Abstract Group Abstract