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.
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.
After entering all my data it was available here.
And give view permissions to the service account used by this website:
The account to share it with is: 489541795175-qu1ub95lv6fj41hiim01f1bdccqo17c8@developer.gserviceaccount.com
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.
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".
Any time this totorial has a code sample, if you are following along on the site, you can use the Example drop down to quickly load the code into your browser.
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.
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.
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
{ "iso": "bee", "size": "Small", "uses": [ "Produced non-meat food" ], "num_uses": 1 }
That looks much better
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.
record.get('name').get('name')
because as
you might notice in the Input box, "name" is actually a dict that
expresses both the language the name is specified in, and the name
of the language itself. In order to get just a string we have to
navigate down the additional layer.
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.
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...
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.
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.
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:
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
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.
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:
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") ] }
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.
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') ] }
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