Tutorial

  1. Create a Google Form
  2. In this example, I went to the Google forms site. You should be able to construct the form using the help provided on the site. Once your form is created be sure to save the results to a Google spreadsheet rather than leaving the result only in forms.

  3. Fill data into the Form
  4. In my case, this was filling out my form for each of my favorite 3 letter animal names that also might happen to be ISO codes for languages.

  5. View your results in Google spreadsheets
  6. After entering all my data it was available here.

  7. Share your results with the Lang Map service account.
  8. Click the Share button on the spreadsheet of form results:

    And give view permissions to the service account used by this website:

    The account to share it with is: 489541795175-qu1ub95lv6fj41hiim01f1bdccqo17c8@developer.gserviceaccount.com

  9. Find your worksheet on the main page
  10. Navigate to the homepage and you should be able to see your worksheet now. Click the link and wait a few seconds while it retrieves data from google sheets.

  11. View your first data row in JSON form
  12. First of all, pardon me if this interface seems sort of clunky. I hope you find this tool useful, but I'm not a front-end or usability expert by any means.

    Once the data have been loaded, you should be able to see the first row of your spreadsheet in JSON form. If you're lucky the site will also have automatically determined that one of the columns of your data was the ISO of the language. If that happened, you should also see some additional language data downloaded from glottolog.org in that box.

  13. Python Round 1: A New Interface
  14. The next thing on the page is an empty box labelled "Output" and finally a dark box for Python code. This box can be used to write python that can be used to process and map the data in your spreadsheet.

    The function named "process" is special. It will be called once for each row in your Google Spreadsheet. This function will take two arguments, 'record' (a dict from your spreadsheet) and 'glotto' (a dict for the corresponding language on glottolog.org).

    To help you visualize what these dicts contain you can look at the Input box.

    For starters, let's write a really simple processing function that just returns the row from our data and press "Run On Single Input".

    def process(record, glotto):
        return record
    

    We see after pressing Run on Single Input that the output is simply the "record" portion of the input as expected.

    That looks good. The keys are a bit long, but we can fix that up later. For now, we've confirmed that this will run on a single input, so let's press "Map All" to run our python on all of the rows. At this point scroll down to the bottom of the page. You'll notice the Map was empty, we'll get to that later, but at the bottom of the page there is a full table, with one entry for each of the processed values.

  15. Python Round 2: Cleaning the Data
  16. Before we get into some of the fancier features of the site. I'd like to start by making the data look a bit cleaner. Specifically, right now the keys of the records are long and sloppy, and I'd like to convert the long keys to shorter ones that are easier to type and remember.

    In addition, I'd like to create some derivative data. Lets try to turn the uses of each animal into a proper python list, and record the number of uses in a different column.

    New Code

    remapping = {
          "letteranimalthatisalsoalanguageisocode": "iso",
          "sizeoftheanimal": "size",
          "whywouldsomeoneraiseorowntheanimal": "uses"
      }
    
    def remap(record):
        result = {}
        for k in record:
            if k in remapping:
                result[remapping[k]] = record[k]
        return result
    
    def process(record, glotto):
        result = remap(record)
        result['uses'] = [] if not result['uses'] else result['uses'].split(',')
        result['num_uses'] = len(result['uses'])
        return result
    

    New Output

    {
        "iso": "bee",
        "size": "Small",
        "uses": [
            "Produced non-meat food"
        ],
        "num_uses": 1
    }
    

    That looks much better

  17. Python Round 3: Maps
  18. Now that we've got a nice clean source of data, it's time to try to put things to the map. While we're at it, we should also probably get the language name for the codes we've invented. To do this, we can simply grab the 'name', 'longitude', and 'latitude' fields from the glotto dict.

    Note that these three fields are special on the LangMap website. If you return a python dictionary with both a 'longitude' and a 'latitude' key it will draw a dot on the world map at that location. If the dictionary that you return has a key named 'name' then that will be the mouseover text when you hover over that dot on the map.

    New Code

    remapping = {
          "letteranimalthatisalsoalanguageisocode": "iso",
          "sizeoftheanimal": "size",
          "whywouldsomeoneraiseorowntheanimal": "uses"
      }
    
    def remap(record):
        result = {}
        for k in record:
            if k in remapping:
                result[remapping[k]] = record[k]
        return result
    
    def process(record, glotto):
        result = remap(record)
        result['uses'] = [] if not result['uses'] else result['uses'].split(',')
        result['num_uses'] = len(result['uses'])
        result['longitude'] = glotto.get('longitude')
        result['latitude'] = glotto.get('latitude')
        result['name'] = glotto.get('name').get('name')
        return result
    

    Then pressing Map All...

    New Map

    TaDa! We have a graph. Unfortunately there are some suspicious dots in the middle of the ocean. Scrolling down to the Table we can see that 'owl' does not have any longitude/latitude data. Instead it seems we just got markers placed at (0,0) for that language. In the next section we'll look at removing that point.

  19. Python Round 4: Filtering
  20. You don't have to return a valid dict for every language you are passed. Instead, if a given language is uninteresting you can just return "None". In our case, we want to return "None" if there is no glotto data for that entry or if we don't have longitude/latitude data for a point, as that will prevent us from being able map them. For real data it might be more interesting to filter the data based on region or features while diving into specific phenomena.

    remapping = {
          "letteranimalthatisalsoalanguageisocode": "iso",
          "sizeoftheanimal": "size",
          "whywouldsomeoneraiseorowntheanimal": "uses"
      }
    
    def remap(record):
        result = {}
        for k in record:
            if k in remapping:
                result[remapping[k]] = record[k]
        return result
    
    def process(record, glotto):
        if not glotto or 'latitude' not in glotto:
            return None
        result = remap(record)
        result['uses'] = [] if not result['uses'] else result['uses'].split(',')
        result['num_uses'] = len(result['uses'])
        result['longitude'] = glotto.get('longitude')
        result['latitude'] = glotto.get('latitude')
        result['name'] = glotto.get('name').get('name')
        return result
    

    Pressing "Map All" and investigating the Map and the Table shows this has had the desired effect.

  21. Python Round 5: Colors
  22. Just like for 'longitude', 'latitude', and 'name' there is also a special key 'color' that can be used to set the color of the dot on the world map. To specify a color, you must specify the color in terms of quantities of red, green, and blue. These values must be expressed from 0 to 255 in a list.

    For example [255, 0, 0] is bright red, [255, 0, 255] is bright purple, and [0, 0, 0] is black. Searching the internet for RGB and Color Pickers will give you a bit of context as to why colors are done this way, as well as give you nice tools to learn the RGB values of any colors you wish to use.

    Using this information we can set the color of each dot based on the size of the animal corresponding to it's ISO code using the following code:

    New Code

    remapping = {
          "letteranimalthatisalsoalanguageisocode": "iso",
          "sizeoftheanimal": "size",
          "whywouldsomeoneraiseorowntheanimal": "uses"
      }
      
    red = [170, 0, 0]
    green = [0, 170, 0]
    blue = [0, 0, 170]
    
    def setColor(result):
        size = result['size']
        if size == 'Small':
            result['color'] = red
        elif size == 'Medium':
            result['color'] = green
        else:
            result['color'] = blue
    
    def remap(record):
        result = {}
        for k in record:
            if k in remapping:
                result[remapping[k]] = record[k]
        return result
    
    def process(record, glotto):
        if not glotto or 'latitude' not in glotto:
            return None
        result = remap(record)
        result['uses'] = [] if not result['uses'] else result['uses'].split(',')
        result['num_uses'] = len(result['uses'])
        result['longitude'] = glotto.get('longitude')
        result['latitude'] = glotto.get('latitude')
        result['name'] = glotto.get('name').get('name')
        setColor(result)
        return result
    

    Colored Map

    In this map I've colored all the Large animals blue, the Medium animals Green, and the Small animals red.

    From this it can clearly be seen that the further West a language is spoken the larger the animal that corresponds to it's ISO code is. I don't think this result should come as a surprise to anyone.

  23. Python Round 6: Adding a Map Key
  24. The above map is useful, but requires the viewer to look at the python in order to determine what the colors mean. Instead, if you intend on showing your map to another person, it's nice to add a legend to explain the meaning of the colors. The LangMap server will let you do that by defining another special function named 'key'

    This function takes no arguments and should return a dict with the following form:

    {
      "name": "Title-Of-The-Key",
      "keys": [ # Array of (color, name) tuples
         ([170, 0, 0], "Red Label Text"),
         ([0, 170, 0], "Green Label Text")
      ]
    }
    

    Again the colors should be specified as arrays of [Red, Green, Blue] as before. Using this tool with our exiting code, the new code becomes:

    Code with Key

    remapping = {
          "letteranimalthatisalsoalanguageisocode": "iso",
          "sizeoftheanimal": "size",
          "whywouldsomeoneraiseorowntheanimal": "uses"
      }
      
    red = [170, 0, 0]
    green = [0, 170, 0]
    blue = [0, 0, 170]
    
    def setColor(result):
        size = result['size']
        if size == 'Small':
            result['color'] = red
        elif size == 'Medium':
            result['color'] = green
        else:
            result['color'] = blue
    
    def remap(record):
        result = {}
        for k in record:
            if k in remapping:
                result[remapping[k]] = record[k]
        return result
    
    def process(record, glotto):
        if not glotto or 'latitude' not in glotto:
            return None
        result = remap(record)
        result['uses'] = [] if not result['uses'] else result['uses'].split(',')
        result['num_uses'] = len(result['uses'])
        result['longitude'] = glotto.get('longitude')
        result['latitude'] = glotto.get('latitude')
        result['name'] = glotto.get('name').get('name')
        setColor(result)
        return result
    
    def key():
        return {
              "name": "Size of Animal",
              "key": [
                     (red, "Small"),
                     (green, "Medium"),
                     (blue, "Large")
                  ]
            }
    

  25. Python Round 7: Genetic information
  26. There are a few additional features that can be helpful for analysing the genetic information of a language. In order to assist with these analyses the genetic inofmration about a language has been extraced and is available in the glotto input argument to proces(). The glotto dict should have a key named 'parent' which represents the glotto entry for the parent of the current language. That entry in turn will have it's own 'parent' all the way back to the the language family.

    Below we've used this information to enhance our visualization. Using the 'symbol' key in the result dict we can draw languages as any capital letter directly on the map. Additionally, note that the key can take single letters in addition to colors to aid the viewer in deciphering the meaning of these letters.

    Final Map

    Final Code

    remapping = {
          'letteranimalthatisalsoalanguageisocode': 'iso',
          'sizeoftheanimal': 'size',
          'whywouldsomeoneraiseorowntheanimal': 'uses'
      }
      
    red = [170, 0, 0]
    green = [0, 170, 0]
    blue = [0, 0, 170]
    
    def setColor(result):
        size = result['size']
        if size == 'Small':
            result['color'] = red
        elif size == 'Medium':
            result['color'] = green
        else:
            result['color'] = blue
    
    def setSymbol(result):
        genetics = result['genetics']
        if 'Indo-European' in genetics:
            result['symbol'] = 'E'
        elif 'Malayo-Polynesian' in genetics:
            result['symbol'] = 'P'
    
    def remap(record):
        result = {}
        for k in record:
            if k in remapping:
                result[remapping[k]] = record[k]
        return result
    
    def getParentList(glotto):
        result = [glotto.get('name').get('name')]
        current = glotto
        while 'parent' in current:
            current = current.get('parent')
            result.append(current.get('name').get('name'))
        return result
    
    def process(record, glotto):
        if not glotto or 'latitude' not in glotto:
            return None
        result = remap(record)
        result['uses'] = [] if not result['uses'] else result['uses'].split(',')
        result['num_uses'] = len(result['uses'])
        result['longitude'] = glotto.get('longitude')
        result['latitude'] = glotto.get('latitude')
        result['name'] = glotto.get('name').get('name')
        result['genetics'] = getParentList(glotto)
        setColor(result)
        setSymbol(result)
        return result
    
    def key():
        return {
              'name': 'Size of Animal',
              'key': [
                     (red, 'Small'),
                     (green, 'Medium'),
                     (blue, 'Large'),
                     ('E', 'Indo-European'),
                     ('P', 'Malayo-Polynesian')
                  ]
            }
    

  27. Closing Words
  28. At this point you should have all the tools you need to write arbitrarily complex python functions to color languages on the world map. If you still feel you need a bit more time and want to see a few more examples before you go out and write your own filters and coloring functions, please feel free to look at other examples from the Example drop down. Perhaps these will already be close to what you are looking for, and you can just change a few things to get the visualization you desire.

    Hopefully this tutorial/guide has been helpful, and if you have any additional questions please feel free to reach out to me at sarum90@gmail.com.

    -Cheers
    Marcus