tag:blogger.com,1999:blog-91304507198819947732024-03-16T18:53:03.805+00:00Paul's Geek Dad BlogI'm a Dad and a Geek. My blog is about teaching my kids to be producers, not just consumers of technology. My tinkering exists somewhere at the intersection between home automation, the internet of things, the maker movement and fitness. It's a nice place to be!
Geek Dadhttp://www.blogger.com/profile/12159682162763730420noreply@blogger.comBlogger98125tag:blogger.com,1999:blog-9130450719881994773.post-31574585558446347592022-02-04T18:39:00.003+00:002022-02-04T18:40:59.204+00:00Cheating at Wordle with Python, NLTK and a Raspberry Pi<p>At the time of writing, the game Wordle is taking the world by storm. Quite rightly, it's genius and so is the guy who invented it. </p><p>Here's one I did earlier:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEiQNQl8daR71Yz0p73oPyqkgOh-FSFaR4CMLsLg7nEEk7HqhGtlh0RYvXyC5NZA-Zf1Hu8kmSAEFtpipr_ADVlQPSyCj-2wur_LBd3PPSu8rWZpAVlAeJm_qzfrGSYU6lLKY25YyHqBRAqQKhs-T8frShqLzvogLqlMIC6U-xiuzCK_hbdWR_L0jQn_Lw=s887" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="887" data-original-width="785" height="320" src="https://blogger.googleusercontent.com/img/a/AVvXsEiQNQl8daR71Yz0p73oPyqkgOh-FSFaR4CMLsLg7nEEk7HqhGtlh0RYvXyC5NZA-Zf1Hu8kmSAEFtpipr_ADVlQPSyCj-2wur_LBd3PPSu8rWZpAVlAeJm_qzfrGSYU6lLKY25YyHqBRAqQKhs-T8frShqLzvogLqlMIC6U-xiuzCK_hbdWR_L0jQn_Lw=s320" width="283" /></a></div><br /><p>Two things to know about me:</p><p></p><ul style="text-align: left;"><li>I'm rubbish with word games.</li><li>I like to trick my children into thinking I'm cleverer than I am.</li></ul><p></p><p>So having struggled a bit with Wordle I wondered if I could write some code to more efficiently (for that read cheat) find Wordle answers. For this I used my trusty Raspberry Pi, Python and the Natural Language ToolKit (NLTK) module. I'd used NLTK previously for some online data science courses so I knew it could give me a "corpus" (so set) of words to play with. </p><p>First I installed NLTK for use with Python 3 on the Raspberry Pi using this command: <span style="font-family: courier; font-size: x-small;">sudo pip3 install nltk</span></p><p>Then I looked at which word corpus NLTK has that I could use. There's some details <a href="https://www.nltk.org/book/ch02.html" target="_blank">here</a> and a few places pointed me to the "Brown" corpus as a good place to start. To make this corpus available for NLTK in a Python script I opened a Python3 shell and ran these commands:</p><div style="text-align: left;"><span style="font-family: courier; font-size: x-small;">Python 3.7.3 (default, Jan 22 2021, 20:04:44)<br /></span><span style="font-family: courier; font-size: x-small;">[GCC 8.3.0] on linux<br /></span><span style="font-family: courier; font-size: x-small;">Type "help", "copyright", "credits" or "license" for more information.<br /></span><span style="font-family: courier; font-size: x-small;">>>> import nltk<br /></span><span style="font-family: courier; font-size: x-small;">>>> nltk.download('brown')</span></div><div><br /></div><div style="text-align: left;">So that sets a corpus of words ready to use in a Python script. I won't go into the detail of how Wordle is played but overall you get 6 goes to guess a 5 letter word. With each guess, the game tells you for each letter:<br /><ul style="text-align: left;"><li>Whether it is in the final word in the exact position you guessed it it, I call these <u>exact matches</u>.</li><li>Whether it is in the final word but not in the exact position you guessed it in. I call these <u>partial matches</u>.</li><li>Whether it is not in the final word. I call these <u>non-matches</u>.</li></ul></div><div>I then played around with snippets of code to examine words from the corpus and rule them in or out based upon whether they had exact matches in them or non-matches not in them. That led me overall to an algorithm of:</div><div><br /></div><div>-Load word corpuses (at the time of writing I use Brown, Webtext and Gutenberg)</div><div>-Build a dictionary of 5 letter words and their frequency of occurrence in the corpus</div><div>-Setup data structures to log exact, partial and non-matches.</div><div><br /></div><div>-Loop at least six times doing:</div><blockquote style="border: none; margin: 0px 0px 0px 40px; padding: 0px; text-align: left;"><div>1-Make a prediction (which I then enter into Wordle) based on the data structures</div><div>2-Input the result of the prediction from Wordle</div><div>3-Update some data structures</div></blockquote><div><br /></div><div>(Full code is at the end of this post)</div><div><br /></div><div>Taking <b>step 2</b> above first, I simply enter the result of each Wordle round in a coded string. So take this result:</div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/a/AVvXsEg6OoQ3mA2pvapNb3raBfMY1GQRIUXsht22ayVA1UsLmDEyt62XgnAGlxRuajpJzFhZodNrdSgwXj_H6lZ6PHZd4-OaFSUKoJmwxi0gOjEt5n_TLGlNUqwBe4lwi0C7Ihi1BVcjZP_357Lxm4hJQA5Ks5hFo7CE8eqae2yxlNhMFDoGW_5hN51Z07OmIg=s434" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="88" data-original-width="434" height="65" src="https://blogger.googleusercontent.com/img/a/AVvXsEg6OoQ3mA2pvapNb3raBfMY1GQRIUXsht22ayVA1UsLmDEyt62XgnAGlxRuajpJzFhZodNrdSgwXj_H6lZ6PHZd4-OaFSUKoJmwxi0gOjEt5n_TLGlNUqwBe4lwi0C7Ihi1BVcjZP_357Lxm4hJQA5Ks5hFo7CE8eqae2yxlNhMFDoGW_5hN51Z07OmIg=s320" width="320" /></a></div><br /><div>I enter this as S_E,U_N,G_N,A_P,R_P. Where _E is for exact, _N is for non-matched and _P is for partial.</div><div><br /></div><div>Looking at <b>step 3</b>, I update 3 data structures based upon the input the user provides. The data structures are:</div><div><ul style="text-align: left;"><li>A tuple of exact matches where the tuple contains the letter and the exact match position. So from the above it would be [('S', 0)].</li><li>A dictionary of partial matches where the key is the letter and the value is a list of positions that the letter is <b>not</b> in. S from the above it would be {'A': [3], 'R': [4]}</li><li>A list of non-matches. So from the above it would be ['U', 'G']</li></ul></div><div>If it's an exact match I do two things:</div><div>1)Update the tuple of exact matches with the new matched letter/position combination.</div><div>2)Update the partial match dictionary as this is a a)new position a partial matched letter can't be in and b)it may require removal of an entry as what was previously partially matched is now fully matched.</div><div><br /></div><div>If it's a partial match I do two things:</div><div>1)Add a new partial (with the letter position) or update the list of positions for an existing partial</div><div>2)If it's a new partial, don't just add the position it is in, add the position of all the other exact matches as the letter can't be in this position either.</div><div><br /></div><div>If it's a non-match I just add the letter to the list of non-matches.</div><div><br /></div><div>So for game above, the log output showed this:</div><div><div><span style="font-family: courier; font-size: x-small;">Enter the result of that round in format A_E,B_P,C_N where _E for exact, _P for partial, _N for non matched:S_E,U_N,G_N,A_P,R_P</span></div><div><span style="font-family: courier; font-size: x-small;">########Processing an exact match for letter S</span></div><div><span style="font-family: courier; font-size: x-small;">########Processing a non match for letter U</span></div><div><span style="font-family: courier; font-size: x-small;">########Processing a non match for letter G</span></div><div><span style="font-family: courier; font-size: x-small;">########Processing a partial match for letter A</span></div><div><span style="font-family: courier; font-size: x-small;">########Processing a partial match for letter R</span></div><div><span style="font-family: courier; font-size: x-small;">########Exact matches [('S', 0)]</span></div><div><span style="font-family: courier; font-size: x-small;">########Partial matches {'A': [0, 1, 3], 'R': [1, 2, 4]}</span></div><div><span style="font-family: courier; font-size: x-small;">########Non matches ['O', 'E', 'P', 'T', 'U', 'G']</span></div></div><div><br /></div><div>Finally looking at <b>step 1</b>, for round 1 I recommend a initial word based upon the following logic:</div><div style="text-align: left;">1)Do a letter frequency count across all the 5 letter words in the corpus.<br />2)Find a word in the corpus that has each of the 5 most common letters. This leads me to use "AROSE".</div><div><br /></div><div>Then for subsequent rounds I do the following:</div><div>1)Eliminate any words from the corpus that have the letters in the non-matching list.</div><div>2)Eliminate any words from the corpus that don't have the exact matching letters in the exact matching position.</div><div>3)Eliminate any words from corpus that don't have the partial matching letters or, if they do, have the partial matching letters in the positions logged that they can't be in.</div><div><br /></div><div>...which results in a list of words which I augment with the frequency of occurrence in the corpus. I then print this and let the use choose the word to enter next. </div><div><br /></div><div>The story so far is that I have played 4 games with this code and:</div><div>1)I have got the right answer every time, but</div><div>2)My 17 year old daughter has got the right answer in fewer guesses 3 times out of 4!</div><p>Full code listing:</p><p><br /></p><p><br /></p>
<pre><code class="python">
#!/usr/bin/env python3
from nltk.corpus import brown
from nltk.corpus import webtext
from nltk.corpus import gutenberg
import sys
#Our main 5 letter word list
five_letters = {} #A dictionary of 5 letter words with frequencies
#Gets a list of 5 letter words
def add_to_list(in_word_list):
for word in in_word_list:
if word.isalpha():
if len(word) == 5:
if word.upper() not in five_letters: #as variety of case of same word could be present
five_letters[word.upper()] = 1
else:
five_letters[word.upper()] += 1
#Compares our 5 letter word corpus with the list of letter frequencies to find the best start word
#The best word has all the highest frequency letters just once
def get_start_words(letter_frequencies, start_pos):
words_found = 0 #Counts when our set of 5 high frequency letters matches a word from our corpus
list_start_pos = start_pos #Used to track where we are in our letter frequency list
word_list = []
#Loop until we've found words from the word corpus or we've exhausted the
while words_found == 0 and list_start_pos < 22: #It's 22 as this will mean we've got to positions 21,22,23,24,25 in the character frequency list
#Loop for each of the words in the corpus
for my_word in five_letters:
letter_count = 0 #Incremented if we find a letter in the word
#Then for each of the five letters identified by the outer while loop
for i in range (list_start_pos, list_start_pos + 5):
my_letter = letter_frequencies[i][0]
if my_letter in my_word:
letter_count += 1 #Count if the letter is in the word
#return word_list
#See if we found all our letters in the word. So if one letter is there twice we should not
if letter_count == 5:
word_list.append(my_word)
words_found += 1
list_start_pos += 1
#Return our word list
return word_list
print ("########Building five letter word list")
add_to_list(brown.words())
print ("Added Brown corpus and word list has {} entries".format(len(five_letters)))
add_to_list(webtext.words())
print ("Added Webtext corpus and word list has {} entries".format(len(five_letters)))
add_to_list(gutenberg.words())
print ("Added Gutenberg corpus and word list has {} entries".format(len(five_letters)))
#Calculate letter frequencies
print ("########Calculating letter frequencies")
freq_dict = {}
for my_word in five_letters:
#Build letter frequencies
for my_letter in my_word:
#See of the letter is a key in the dictionary
if my_letter in freq_dict.keys():
freq_dict[my_letter] += 1 #Increment value
else:
freq_dict[my_letter] = 1 #Add value
#Sort the dictionary to get the highest probability letters and show the user
sorted_values = sorted(freq_dict.items(), key=lambda x:x[1], reverse=True)
print (sorted_values)
#Holds the round number
round_number = 1
#Holds the exact matches. A list of tuples
exact_matches = []
#Holds the partial matches. A dictionary of key is letter, value is list of positions letter is not in
partial_matches = {}
#Holds the non-matched letters. A list
non_matches = []
#Loop for all the rounds
while round_number < 7 and len(exact_matches) < 5:
print ("######################################################")
print ("########This is round number {}".format(round_number))
#Special case for round 1
if round_number == 1:
#Get a list of possible starting words based upon the letter frequencies
print ("########Getting a list of words to start off with")
#Imagine we get 6 wrong starting words! Accoutn for each
for k in range (0,3):
start_words = get_start_words(sorted_values, k)
print ("Where we start at position {} of the letter frequencies the start words are: {}".format(k, start_words))
else:
#Step 1, rule out a bunch of words that have eliminated letters in them
print ("########Assessing data from previous round to make a recommendation")
print ("########First rule out words based on letters found not to exist in the answer")
after_non_match_check = []
for my_word in five_letters:
has_ruled_out = False
for letter in non_matches:
if letter in my_word:
has_ruled_out = True
if not has_ruled_out:
after_non_match_check.append(my_word)
print ("########At the end of this we are down to {} words".format(len(after_non_match_check)))
#Step 2, rule in a set of words based on the matched list
print ("########Second rule in words based on exact matched letters")
after_full_match_check = []
for my_word in after_non_match_check:
has_ruled_out = False
for match_tuple in exact_matches:
if my_word[match_tuple[1]] != match_tuple[0]:
has_ruled_out = True
if not has_ruled_out:
after_full_match_check.append(my_word)
print ("########At the end of this we are down to {} words".format(len(after_full_match_check)))
#print (after_full_match_check)
#Step 3, rule out a set of words where letters are in partial match positions
print ("########Third rule in words based on partial matched letters")
after_partial_match_check = []
for my_word in after_full_match_check:
has_ruled_out = False
for my_partial in partial_matches: #Loop through each dictionary entry
#First simply check the partial is in the word
if my_partial in my_word:
#Now check for the partial positions
for my_partial_pos in partial_matches[my_partial]: #Loop through each item in the partial match
if my_partial == my_word[my_partial_pos]:
has_ruled_out = True
else:
has_ruled_out = True
if not has_ruled_out:
after_partial_match_check.append(my_word)
print ("########At the end of this we are down to {} words. Recommendation:".format(len(after_partial_match_check)))
#Form an ordered list of words based on overall word frequency from the corpus that was built right at the start
suggestion_dict = {}
ordered_suggestion = {}
for word_suggestion in after_partial_match_check:
suggestion_dict[word_suggestion] = five_letters[word_suggestion]
#Order the suggestions
ordered_suggestion = sorted(suggestion_dict.items(), key=lambda x:x[1], reverse=True)
#Tell the user
if len(ordered_suggestion) < 11:
print (ordered_suggestion)
else:
#Print just the first 10
print (list(ordered_suggestion)[:10])
#Get input from the user as to what happened in that round
round_result = input("Enter the result of that round in format A_E,B_P,C_N where _E for exact, _P for partial, _N for non matched:")
#Pull round result apart and process each
result_list = round_result.split(",")
#Loop for each result
letter_pos = 0 #Holds which letter result position we're looking at
for result in result_list:
#Get the entered letter and the result
entered_letter = result[0]
letter_result = result[2]
if letter_result == "E":
print("########Processing an exact match for letter {}".format(entered_letter))
#See if we already have this exact match. If not, add it
letter_found = False
for my_tuple in exact_matches:
if my_tuple[0] == entered_letter and my_tuple[1] == letter_pos: #So we could have double letters so this checks for existence of the letter in the given position
letter_found = True
if not letter_found:
exact_matches.append((entered_letter,letter_pos))
#Update existing partial matches as well, i.e. 1)They can't be in the position of the found letter. Also, if there is already a partial for what is now exact, remove it
if entered_letter in partial_matches:
partial_matches.pop(entered_letter)
else:
for partial in partial_matches:
if letter_pos not in partial_matches[partial]:
partial_matches[partial].append(letter_pos)
elif letter_result == "P":
print("########Processing a partial match for letter {}".format(entered_letter))
if entered_letter in partial_matches:
#Look partial record, seeing if the current position is in the position list. If not, add it
if letter_pos not in partial_matches[entered_letter]:
partial_matches[entered_letter].append(letter_pos)
else:
#Entered letter not in partial matches dictionary. Add it together with the position
partial_list = []
partial_list.append(letter_pos)
#But as this is a new partial we also need to add all existing exact matches which the letter can also not be in that position
for exact in exact_matches:
partial_list.append(exact[1])
#FInal update of the partial list
partial_matches[entered_letter] = partial_list
elif letter_result == "N":
print("########Processing a non match for letter {}".format(entered_letter))
#See if we already have this non-match. If not, add it
if entered_letter not in non_matches:
non_matches.append(entered_letter)
#Update so we get the next letter position
letter_pos +=1
#Show what the structures are at the end of this round
print ("########Exact matches {}".format(exact_matches))
print ("########Partial matches {}".format(partial_matches))
print ("########Non matches {}".format(non_matches))
#End of turn Update for next loop
round_number += 1
#See how we came out of the main loop
if len(exact_matches) == 5:
print ("########You won! Way to go/cheat")
else:
print ("########All rounds completed, you lost!")
</code></pre>
Geek Dadhttp://www.blogger.com/profile/12159682162763730420noreply@blogger.com0tag:blogger.com,1999:blog-9130450719881994773.post-86299603494143599382021-04-03T15:28:00.001+01:002021-04-03T15:28:41.826+01:00Excel Basics - Autofill<p>Ever noticed the tiny little square on the bottom right of the rectangle that goes around the cell you have selected? This is called <b>Autofill</b> and it's <b>super useful</b>! It's circled in the image below:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgRP6h7EUhjcpDXcz3Ib5yhTZDO0uhkQJo4nsMjNxsBH71r14ZmH63uYVtLLtJTFiHQloaLf2S0N95mZf9Ofq8O7gJPgH4FWVgl5keahyphenhyphenmaOX41JVweSaIXSMv7X_pEaR4KRWcxaQPJ9FgJ/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="272" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgRP6h7EUhjcpDXcz3Ib5yhTZDO0uhkQJo4nsMjNxsBH71r14ZmH63uYVtLLtJTFiHQloaLf2S0N95mZf9Ofq8O7gJPgH4FWVgl5keahyphenhyphenmaOX41JVweSaIXSMv7X_pEaR4KRWcxaQPJ9FgJ/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>So what does it do? Hover over it and a little black plus appears. Click and drag it down (or across) and it acts like copy and paste. Here's a simple example. I typed 100 in cell A1, clicked and dragged the Autofill control down to A9 and the value 100 was populated in every cell:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJrBG3HPx6-mDnRutYJoS7smfQ416HlJRR6191Eqih9oUP-LJ5V-c8jjQEUqNYzb-AoiMwBsptVBmkmdBPnsbGnJYKvnPcKDJdjhSQWDEadAK6w4XoEficGN6WFm3U4XyYG4pz-ONLnXHk/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="326" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgJrBG3HPx6-mDnRutYJoS7smfQ416HlJRR6191Eqih9oUP-LJ5V-c8jjQEUqNYzb-AoiMwBsptVBmkmdBPnsbGnJYKvnPcKDJdjhSQWDEadAK6w4XoEficGN6WFm3U4XyYG4pz-ONLnXHk/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>But autofill is cleverer than that. I put a 1 in cell B1, a 2 in cell B2, selected both cells, clicked the Autofill control and dragged down:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3dFhF5bvAsJj5m3bujbWuNXEnJuwkRbxbtZZRQM1uKAP0T3xkWkPckR6-uLfhKKCEJExjSeNexCCpGhjm7kB2nVnfr9HqE4Dbe_B5yyHv71ZVZZYjTjq9ougzpbKW00NQAEzu3jb1JpsK/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="358" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh3dFhF5bvAsJj5m3bujbWuNXEnJuwkRbxbtZZRQM1uKAP0T3xkWkPckR6-uLfhKKCEJExjSeNexCCpGhjm7kB2nVnfr9HqE4Dbe_B5yyHv71ZVZZYjTjq9ougzpbKW00NQAEzu3jb1JpsK/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>Here autofill has said "hang on a minute, there's a 1 in the first row, a 2 in the second row, I'll continue the sequence".</p><p>You can use it to Autofill formulae in cells. In the image below I put "=A1+B1" in cell C1 then used Autofill to copy it down:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVY6bK4ofys3nsEj7JWjrNDq8L82PqiL1NKGngA-OsTYBp8vBuSSrKx4wwF3qMWeq5OMfIqusnlVsao2UvQu6le3vQOBW5nuW-0KgeMRSBFYguBo0C4lqNExcAFQUQnJi5gZQwZ6Q7HjX-/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="320" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjVY6bK4ofys3nsEj7JWjrNDq8L82PqiL1NKGngA-OsTYBp8vBuSSrKx4wwF3qMWeq5OMfIqusnlVsao2UvQu6le3vQOBW5nuW-0KgeMRSBFYguBo0C4lqNExcAFQUQnJi5gZQwZ6Q7HjX-/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>Autofill will also make intelligent decisions if you have text in a cell. In the example below, I typed "Row 1" in cell D1 then Autofilled down. Autofill decides to increment the number at the end of the text as you go down:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOQVx_O9J1Y3StROxFf1gk33fGFB6M_vLZRnHHgUD0t2Khcv9sTWoO2wgNhs1mExOkfGxjT2BppMjHDrsYq-ZozRw5H71fb5mJigxZejAXBqYIwaFNCfHNlaPDL0syKV6TE2srCFbfHCiR/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="320" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgOQVx_O9J1Y3StROxFf1gk33fGFB6M_vLZRnHHgUD0t2Khcv9sTWoO2wgNhs1mExOkfGxjT2BppMjHDrsYq-ZozRw5H71fb5mJigxZejAXBqYIwaFNCfHNlaPDL0syKV6TE2srCFbfHCiR/s16000/image.png" /></a></div><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p>If you've been watching carefully you'll have seen this little icon appear at the bottom of the set of cells you've been using Autofill on: <div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLIJPLQ2YCEAV4i9OCMnDKBNbNAL5OI2_zCqboqdkzk20JBdVEdCpQ0cdduh28qlxtkNZTSO_qGjjsGTxRLK5dMytC5buk3cVlDlkGt3doP91H3maDt6-4Iw-UgRgkny4DeXjo5VdZkACh/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="50" data-original-width="51" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhLIJPLQ2YCEAV4i9OCMnDKBNbNAL5OI2_zCqboqdkzk20JBdVEdCpQ0cdduh28qlxtkNZTSO_qGjjsGTxRLK5dMytC5buk3cVlDlkGt3doP91H3maDt6-4Iw-UgRgkny4DeXjo5VdZkACh/s16000/image.png" /></a></div><div class="separator" style="clear: both; text-align: left;">Click on it and you get a bunch of different options for how Autofill works. The example above is "Fill Series". If you select "Copy Cells" you get:</div><div class="separator" style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHYN-tdPFV6fkxhpcv9NV7-DyoUaG6B9k16FWX08ubqvi9Me0Fv1Eq4BQa92i2woNAuP-Hw5JlieuvdIfY-UCK1Dv4nlFNA88bmo3Oa7-vbEO9gUt8xRJNubw_TsQ7yRQ50W38ah3V15gs/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="318" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHYN-tdPFV6fkxhpcv9NV7-DyoUaG6B9k16FWX08ubqvi9Me0Fv1Eq4BQa92i2woNAuP-Hw5JlieuvdIfY-UCK1Dv4nlFNA88bmo3Oa7-vbEO9gUt8xRJNubw_TsQ7yRQ50W38ah3V15gs/s16000/image.png" /></a></div><br /><br /></div><div class="separator" style="clear: both; text-align: left;">...so a straight copy of what you first entered.</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">If you select "Fill Formatting Only" you get:</div><div class="separator" style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDlVEs-2O0tu-uaIdnWP0pKICuzOW4QIAZNKWZG3wZBtMNkNX2nbGJOUoNdqsPlRJqN1lO5F7IgQ77mVUwSxMF_OfpPE4ks712YwuBw-ir-ZpTEEz9CP2VYqwT7HBPuZx3d7UvC5UuSAfA/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="316" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiDlVEs-2O0tu-uaIdnWP0pKICuzOW4QIAZNKWZG3wZBtMNkNX2nbGJOUoNdqsPlRJqN1lO5F7IgQ77mVUwSxMF_OfpPE4ks712YwuBw-ir-ZpTEEz9CP2VYqwT7HBPuZx3d7UvC5UuSAfA/s16000/image.png" /></a></div><br /><br /></div><br /><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>...so in this case no obvious formatting. But if you did have some specific formatting in cell D1 it would be replicated to the cells below.</p><p>"Fill Without Formatting" looks to give you the same as "Fill Series" but (assuming you had some obvious formatting in cell D1) it would not be replicated down.</p><p>The final "Flash Fill" option is very cool. Say you have set up a worksheet like this; here you have a name of James Bond actors in column A and you've given Excel as hint as to what you want to do with these values in cells B1 and C1:</p><p></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJy-k7LGSKSlz55w_H3tucQaJ-VF_lFLK7CfDIYS4g_tGMbOBwFYERJ3KIcXKljXYcU4_X4d7TP9Ipwmf8pFHfyw9hIGtaOBezAnVY2UzmzYKYmWyW_B2eD9VvpVSizZb9MmRJp5rWjBqa/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="318" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiJy-k7LGSKSlz55w_H3tucQaJ-VF_lFLK7CfDIYS4g_tGMbOBwFYERJ3KIcXKljXYcU4_X4d7TP9Ipwmf8pFHfyw9hIGtaOBezAnVY2UzmzYKYmWyW_B2eD9VvpVSizZb9MmRJp5rWjBqa/s16000/image.png" /></a></div><div class="separator" style="clear: both; text-align: left;">Autofill down and you get a default "Copy Cells" view:</div><div class="separator" style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjz-kanWZ3mga8RN4dxmxdsaSeFYqbWOWd7gWwZkQiDr6FkWW-5zxUh1HN-Zdh12iLrO0z-atdVCpuVKFkt-wXHHF85Ugu8LONYyBTovdG0MDKV-sDEQhJHSRPLNDYC_W4mw3amrTkXrTv4/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="319" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjz-kanWZ3mga8RN4dxmxdsaSeFYqbWOWd7gWwZkQiDr6FkWW-5zxUh1HN-Zdh12iLrO0z-atdVCpuVKFkt-wXHHF85Ugu8LONYyBTovdG0MDKV-sDEQhJHSRPLNDYC_W4mw3amrTkXrTv4/s16000/image.png" /></a></div><br /><br /></div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">But then select "Flash Fill" and you get:</div><div class="separator" style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDczkMengOIR2hjypPYGB_nR5O0cbB0bckjBhWdRGy8L3KhLIlRRmQxiyWUDcCk2FyOi5l4WiCffmWKNkB8TXoZad-dKudEIUu6FspEid7C0zZ9Jkf2EGOR8id54_pFEnMVJA9X88u5TSo/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="320" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDczkMengOIR2hjypPYGB_nR5O0cbB0bckjBhWdRGy8L3KhLIlRRmQxiyWUDcCk2FyOi5l4WiCffmWKNkB8TXoZad-dKudEIUu6FspEid7C0zZ9Jkf2EGOR8id54_pFEnMVJA9X88u5TSo/s16000/image.png" /></a></div><br /><br /></div><div class="separator" style="clear: both; text-align: left;">Repeat for the second name and you get:</div><div class="separator" style="clear: both; text-align: left;"><span style="text-align: center;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhggH3nMdsmdah7qfoJFyzZqPzQ3qtWOcr8enjUnKaWGnVhlKfH6_WwBcL1wY68uxn-ULsNpudai9FMVKuo7o3g0ClpQUyisJcqqfa6iV2oq8DyvpSnbCn5pnT6OgDUo1cthiLlVDdE90NR/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="315" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhggH3nMdsmdah7qfoJFyzZqPzQ3qtWOcr8enjUnKaWGnVhlKfH6_WwBcL1wY68uxn-ULsNpudai9FMVKuo7o3g0ClpQUyisJcqqfa6iV2oq8DyvpSnbCn5pnT6OgDUo1cthiLlVDdE90NR/s16000/image.png" /></a></div><br /><br /></span></div><div class="separator" style="clear: both; text-align: left;"><span style="text-align: center;">Clever Excel!</span></div><br /><br /><br /><p></p>Geek Dadhttp://www.blogger.com/profile/12159682162763730420noreply@blogger.com0tag:blogger.com,1999:blog-9130450719881994773.post-9667793351980459512021-04-02T16:23:00.003+01:002021-04-02T16:26:04.680+01:00Excel Hack - Getting a Newline in an Excel Cell When Entering Text<p>Excel is best for numbers but sometimes you find yourself entering lots of text in a cell. To format the text it's helpful to be able to put in a newline character (aka do a carriage return). So here's how a bunch of text can look without doing this:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5X_Tg7tiYEuartMWegaeljydFW6_IDQMt2JAAImGzbz5jWPH5Vxn89AVEkvclp8Ra93DvWNgmbx58MDvrf8BZcHai5ALxDljCCjr1OiwCa_lDw2N72vU3Q00-1OJ4hZvKDqg1SG2dbezy/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="281" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh5X_Tg7tiYEuartMWegaeljydFW6_IDQMt2JAAImGzbz5jWPH5Vxn89AVEkvclp8Ra93DvWNgmbx58MDvrf8BZcHai5ALxDljCCjr1OiwCa_lDw2N72vU3Q00-1OJ4hZvKDqg1SG2dbezy/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>Put in newline characters and hey presto, all a lot easier to read:</p><p></p><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgox1qVLvzwNDjnXWcZbPINUKiUsR02M9QEiBVoENHcjxg0B2WDUEmcRvVBmf7PNmf677N8Ydaj9Snd-Ztis9gcmiYwMvjv9w5ltvplk1tQVhW2ospq-m6c5kkkDcmw2qYGywG2-R5OmG5S/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="342" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgox1qVLvzwNDjnXWcZbPINUKiUsR02M9QEiBVoENHcjxg0B2WDUEmcRvVBmf7PNmf677N8Ydaj9Snd-Ztis9gcmiYwMvjv9w5ltvplk1tQVhW2ospq-m6c5kkkDcmw2qYGywG2-R5OmG5S/s16000/image.png" /></a></div><br /><br /></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>So how to do this? The answer is:</p><p></p><ol style="text-align: left;"><li>Position the cursor where you want the newline, </li><li>Hold down the alt key, </li><li>Press the return key, </li><li>Release the alt key.</li></ol><p></p><p></p><div class="separator" style="clear: both; text-align: left;">Here's where to find the alt key:</div><div class="separator" style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMCzwWPHiqBjUG2vQ_-h61zXoAt-Wa4LlE8r9Ggwh72VJDppzwh8om6fnbmmouD_tNdgf8RJYTWmFlKqxXIM8ROv86NFmLYuKaN0YjzdTuDnb4Sm_SbBrtaWZw3Fdc6wwZCWD-9C6KcOuS/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="223" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgMCzwWPHiqBjUG2vQ_-h61zXoAt-Wa4LlE8r9Ggwh72VJDppzwh8om6fnbmmouD_tNdgf8RJYTWmFlKqxXIM8ROv86NFmLYuKaN0YjzdTuDnb4Sm_SbBrtaWZw3Fdc6wwZCWD-9C6KcOuS/s16000/image.png" /></a></div><br /><br /></div><div class="separator" style="clear: both; text-align: left;">...and here's return (also known as Enter):</div><div class="separator" style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjWblBifgb1oAt2b8S7pFQM_QYG-xpiEnF9Qj8SspdoeN_3q5lYv1aHPXknBF19nPQwb0O4fQFbF5ZasQdD7olg7n7heBUd7Bd7gQVqiRJq7OGFUAGDKeYdINZ6_hRFOmIoWXRU93a1TLTY/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="222" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjWblBifgb1oAt2b8S7pFQM_QYG-xpiEnF9Qj8SspdoeN_3q5lYv1aHPXknBF19nPQwb0O4fQFbF5ZasQdD7olg7n7heBUd7Bd7gQVqiRJq7OGFUAGDKeYdINZ6_hRFOmIoWXRU93a1TLTY/s16000/image.png" /></a></div><br /><br /></div><div class="separator" style="clear: both; text-align: left;">Hope this is useful.</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: center;"><br /></div><br /><br /></div><br /><br /><p></p><p><br /></p>Geek Dadhttp://www.blogger.com/profile/12159682162763730420noreply@blogger.com0tag:blogger.com,1999:blog-9130450719881994773.post-65952833219616313012021-03-30T18:25:00.000+01:002021-03-30T18:25:39.322+01:00Excel Hack - Using a "Magic" Apostrophe to Force a Cell to be Text <p>In Excel you can prefix values in cells with an apostrophe to force Excel to interpret them as text. Why do this you may ask?</p><p>To explain, let's say I want to enter James Bond's telephone number into a spreadsheet. All I have to do is type 01234007007 (which everyone knows is his number) into a cell, right? Wrong! Let's see it in action. First I type his number (numbers in the UK always start with a 0, that includes MI6 spies):</p><p></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfNQNO3EISBxBSp1qEq3QO0zC7NzIcVEI1kvXUqlUS9QZ9Bkz40F37Q6hEspCO0xnrmaZU6cnn0z9EiV5FBXUAmKFaBg2XE_7vxYdtDY_a6LRhrkJuhjC9NHys_cbxFj15whuTwr0bPojw/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="355" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjfNQNO3EISBxBSp1qEq3QO0zC7NzIcVEI1kvXUqlUS9QZ9Bkz40F37Q6hEspCO0xnrmaZU6cnn0z9EiV5FBXUAmKFaBg2XE_7vxYdtDY_a6LRhrkJuhjC9NHys_cbxFj15whuTwr0bPojw/s16000/image.png" /></a></div><br /><br /><p></p>Because of the leading zero, when I press return, Excel strips the first 0 from what I've typed:<div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEindQNVkq1eIca8WAt9NXeIWLSOrNyCqgF8XSvv6EZcFAhbbfIuGPjSQktqndLUjvqJqG5Xv1E2lMlD-dDVL2PumIZRDObST4KHONxDOLgrjdZ1wNTOpWAAsaiifsZX1qeBLkALmN5h7p9K/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="360" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEindQNVkq1eIca8WAt9NXeIWLSOrNyCqgF8XSvv6EZcFAhbbfIuGPjSQktqndLUjvqJqG5Xv1E2lMlD-dDVL2PumIZRDObST4KHONxDOLgrjdZ1wNTOpWAAsaiifsZX1qeBLkALmN5h7p9K/s16000/image.png" /></a></div><br /><br /><div><br /><p></p><p></p><div class="separator" style="clear: both; text-align: left;">I can get around this by prefixing 007's number with an apostrophe (so a '). Here we go:</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjH3C50CBZcKNjSZ2FY7XR0HBv0whJgDz4_Zjara6XZrFtidPHqkeNdS_wdg1QgkqzkZjkXehRsqhedixI7XaNJuQPXuIaPH9YHCH8bOOepelWLvM_laPA9tt-wsW1cMx19lPELp4rINfDm/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="353" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjH3C50CBZcKNjSZ2FY7XR0HBv0whJgDz4_Zjara6XZrFtidPHqkeNdS_wdg1QgkqzkZjkXehRsqhedixI7XaNJuQPXuIaPH9YHCH8bOOepelWLvM_laPA9tt-wsW1cMx19lPELp4rINfDm/s16000/image.png" /></a></div><br /><br /></div><div class="separator" style="clear: both; text-align: left;">In the image above you can see how the apostrophe is not visible in the cell itself but is in the formula bar at the top right.</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">Note you also get a little helpful hint warning you there's an apostrophe there. If you hover over the exclamation mark you get a pop up that says "The number in this cell is formatted as text or preceded by an apostrophe".</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">So I often use the apostrophe trick for phone numbers or to put formatting in cells that Excel may complain about. Say I want to write some text in a cell with several sub-points. You can't do bullet points in a cell so I often prefix lines with a hyphen. So I type this in:</div><div class="separator" style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgURMbK8p9HM5c270_bTsWyqAlywvSxFnPhwb_w8P1wPcZ_hIt-n7P2mu_F09MXl_E0QP57wcUvOmFsusiUzJAmdoIqtR9tYAGDlsLj-lhtfsm6kHIaSf268ZBqmFPvF7I_WAV-Hubb4241/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="350" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgURMbK8p9HM5c270_bTsWyqAlywvSxFnPhwb_w8P1wPcZ_hIt-n7P2mu_F09MXl_E0QP57wcUvOmFsusiUzJAmdoIqtR9tYAGDlsLj-lhtfsm6kHIaSf268ZBqmFPvF7I_WAV-Hubb4241/s16000/image.png" /></a></div><br /><br /><br /><br /></div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">Hit return and I get:</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhdhQ2DRJ5orso9gj_wjjH9pKPuRvJNvV5zUafsrWpLLQ0Y-oEr5vDYBMU70vyL26Do1I4ZYkbhNZA-cL1W4yqZIJEciBwxIeMSalGssDsbwlciNizswSYEVpOv91w7fDhJ56Ub0JWmj0k/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="212" data-original-width="454" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjhdhQ2DRJ5orso9gj_wjjH9pKPuRvJNvV5zUafsrWpLLQ0Y-oEr5vDYBMU70vyL26Do1I4ZYkbhNZA-cL1W4yqZIJEciBwxIeMSalGssDsbwlciNizswSYEVpOv91w7fDhJ56Ub0JWmj0k/s16000/image.png" /></a></div><div class="separator" style="clear: both; text-align: left;">Oh no! Because I start with a hyphen, Excel thinks it's a formula with a minus sign, can't interpret the rest of the formula and throws an error. Put in a magic apostrophe and you get:</div><div class="separator" style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIYaJwiJWX0rEvmhbSN93ebckGzborF9HfLlpE7qCYXUwbhVjRQkouRHAvUz4o6pXblL5i__itiqrIlz0SN0hJQKYOIKoRolXXY_cvckaQONQQRGBPcoyH1QDlxY3wgE70zw-QNzU7sMcd/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="328" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIYaJwiJWX0rEvmhbSN93ebckGzborF9HfLlpE7qCYXUwbhVjRQkouRHAvUz4o6pXblL5i__itiqrIlz0SN0hJQKYOIKoRolXXY_cvckaQONQQRGBPcoyH1QDlxY3wgE70zw-QNzU7sMcd/s16000/image.png" /></a></div><br /><br /></div><div class="separator" style="clear: both; text-align: left;">Again you can see the apostrophe in the formula bar (top right of the image) but not the cell itself.</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">Hope that's useful!</div><div class="separator" style="clear: both; text-align: left;"><br /></div><br /><br /></div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;"><br /></div><br /><br /><p></p></div></div>Geek Dadhttp://www.blogger.com/profile/12159682162763730420noreply@blogger.com0tag:blogger.com,1999:blog-9130450719881994773.post-90865301917195946412021-03-28T16:22:00.000+01:002021-03-28T16:22:01.128+01:00Excel Hack - Concatenating (Adding Together) Text in Different Cells<p>Sometimes in Excel you have values (generally text) in different cells that you want to piece together into a single cell. In the example below I have a set of fruits and tasty things I want to join together into a single cell:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisX9pM5gC0LMlOSZmgqYYQR3Eh485PTxHxNSxXC6Jn2RM49acber3mh3_-uYVQD48ddMf8vYnNg6xTmog9Sn4iYjJLJhVQ7DJQVzbXEBWXfrzMJ32rNTPz79SX2xHhThHHAZ4Nq0jZ99QU/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="317" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEisX9pM5gC0LMlOSZmgqYYQR3Eh485PTxHxNSxXC6Jn2RM49acber3mh3_-uYVQD48ddMf8vYnNg6xTmog9Sn4iYjJLJhVQ7DJQVzbXEBWXfrzMJ32rNTPz79SX2xHhThHHAZ4Nq0jZ99QU/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>Lovely old Excel gives us two ways to do this. The first way, and the one I always remember, is the "&" operator. In the example below I've already put the formula in cell C1 and am showing the formula structure in cell C2:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh89fM0rc6R9sPClXE9SlBnz155M-URV9igNMRvZFh1gHpSwRd1Oet1Yuua8oe4IfkPqzwCjZK48FBjggwqTOBsPAZNoV8-GDyTYwDr_6yUslYIpICYwyUfUYpuE2RbDWuNIIdNXFk3tum_/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="281" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh89fM0rc6R9sPClXE9SlBnz155M-URV9igNMRvZFh1gHpSwRd1Oet1Yuua8oe4IfkPqzwCjZK48FBjggwqTOBsPAZNoV8-GDyTYwDr_6yUslYIpICYwyUfUYpuE2RbDWuNIIdNXFk3tum_/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>The formula simply means "takes what's in cell A2, add a space to it, then add what's in cell B2".</p><p>The second way is the "CONCAT" formula. Here's an example:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinrkE2zPP0Mtuyk11wsS_tCsubRbH4BZ9ngPPXdnp6hpa1bxUt68_R_I1UDlhNiRDWvMjY8KMHdMTH6iBu2cggrGQfkWC49bOhR_956VKQiadGUZnEZbmbqBPS_SSkj7gsEjrX1mRmMPP2/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="315" data-original-width="602" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEinrkE2zPP0Mtuyk11wsS_tCsubRbH4BZ9ngPPXdnp6hpa1bxUt68_R_I1UDlhNiRDWvMjY8KMHdMTH6iBu2cggrGQfkWC49bOhR_956VKQiadGUZnEZbmbqBPS_SSkj7gsEjrX1mRmMPP2/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>Here the CONCAT formula takes a set of cell references and actual strings and joins them all together into a single result.</p><p>For (Geek Dad style) fun I can first transpose the column B values to a row (<a href="https://pdwhomeautomation.blogspot.com/2021/03/excel-basics-paste-special-transpose-to.html" target="_blank">using this method</a>) and then use mixed references (<a href="https://pdwhomeautomation.blogspot.com/2021/03/excel-basics-using-sign-for-relative.html" target="_blank">explained here</a>) to create a crazy set of food ideas:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiaxF_jN3Gly-ydFwDQpp9IMSUf4lHPDA85Zzg_kybrbQYFX-TkHLMh4acTLc5UXgSr174_hZDCP2rv4afv0vTnv7j-kmuJgsJdwb5OG2m_sTJ3A_yb9Hd5-64JQU6x_wo5X2i0bStMs61W/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="203" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiaxF_jN3Gly-ydFwDQpp9IMSUf4lHPDA85Zzg_kybrbQYFX-TkHLMh4acTLc5UXgSr174_hZDCP2rv4afv0vTnv7j-kmuJgsJdwb5OG2m_sTJ3A_yb9Hd5-64JQU6x_wo5X2i0bStMs61W/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>The mixed references in the formula here are used to keep to column A and row 1 as you copy the formula to other cells. Specifically:</p><p></p><ul style="text-align: left;"><li>$A2 - Keep to column A as you copy across columns, allow the row to increment as you copy down.</li><li>B$1 - Allow the column to increment as you copy across, keep the row as 1 as you copy down.</li></ul><p></p><p>Resulting in:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEivzygpaqQ6cTDsPGwREeaJQFEGGgZe8U1VEuruLKdsgMA66CRj6u8eFkGThiEDdEIcnh5-yWBGWfRpBXEDUT-k_P9bJfJ200VkBIIcD584wthM39TxdFaPQTuZNS7StCr6rthW8mzlpo3F/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="143" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEivzygpaqQ6cTDsPGwREeaJQFEGGgZe8U1VEuruLKdsgMA66CRj6u8eFkGThiEDdEIcnh5-yWBGWfRpBXEDUT-k_P9bJfJ200VkBIIcD584wthM39TxdFaPQTuZNS7StCr6rthW8mzlpo3F/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>Comment below if you've used CONCAT or &. </p><p><br /></p>Geek Dadhttp://www.blogger.com/profile/12159682162763730420noreply@blogger.com0tag:blogger.com,1999:blog-9130450719881994773.post-30638811116822455392021-03-28T16:01:00.001+01:002021-03-28T16:01:20.054+01:00Excel Basics - Paste Special + Transpose to Turn a Column of Values into A Row<p>Sometimes in Excel you have values lined up in a single column and you want to get all the values in a single row. Maybe you want to create a matrix where rows and columns are compared with each other.</p><p>Here's an image with made up data to illustrate this:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVRuhTgqOvRt40aj03NMt_aZUP30-d-w0g6ZBQyCS5ZdogQZt_Qo34UARVC9FZynI4blmdoMkUgSd77UYQR79hqPHZI84jYKsdQ31Erg6eQ_6JoKC9aJ4lRh-NOy7cnC83SyIjUtVdTIvF/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="299" data-original-width="506" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVRuhTgqOvRt40aj03NMt_aZUP30-d-w0g6ZBQyCS5ZdogQZt_Qo34UARVC9FZynI4blmdoMkUgSd77UYQR79hqPHZI84jYKsdQ31Erg6eQ_6JoKC9aJ4lRh-NOy7cnC83SyIjUtVdTIvF/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>Let's say I want all the values in column B to actual be across row 1. So for example "Turnover" goes in cell B1, "Juice" goes in cell C1, "Split" goes in cell D1 and so on. I could move them one by one but that would be a real pain, especially as the number of values in the column grows. Instead I use Paste Special and <b>Transpose</b>. Transpose is the fancy name for changing a row to a column and vice versa.</p><p>First, copy the values you want to transpose by selecting them, right clicking and selecting "Copy":</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsR8EqQ3cRGHb5YrlI1bngv0teooGJn_3NxIy9SmxUaD8f0_FTfG-_QJDX6VWJ40O-qaYEejiMQQKcfLnaLyseOedrP2ABDP68bpY9YVI7mRV9oBvhpFs7rEWwZMocQuvWgqIfV-aFms1y/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="320" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjsR8EqQ3cRGHb5YrlI1bngv0teooGJn_3NxIy9SmxUaD8f0_FTfG-_QJDX6VWJ40O-qaYEejiMQQKcfLnaLyseOedrP2ABDP68bpY9YVI7mRV9oBvhpFs7rEWwZMocQuvWgqIfV-aFms1y/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>Next select the cell where you want the row of values to be Transposed to. In my case I want the first value in cell B1 so I select that. I then right click and select "Paste Special...". The form shown below comes up. The part of the form I'm interested in is circled:<br /></p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEit0H2G07Z0cPt8H-kkIDjmAIZbSW32Orw308aRFrvOoxiIKDzgM82hoFVDP0jmqo8gZ_na6yBYkjr4rqlfyKQlwvkmM8ef2woNbZ2l-TAAWRZifM364arcopx1OAPABnBIcSwv-RknDLks/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="518" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEit0H2G07Z0cPt8H-kkIDjmAIZbSW32Orw308aRFrvOoxiIKDzgM82hoFVDP0jmqo8gZ_na6yBYkjr4rqlfyKQlwvkmM8ef2woNbZ2l-TAAWRZifM364arcopx1OAPABnBIcSwv-RknDLks/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>Tick the "Transpose" box then hit OK. Lo and behold, what was in the column is now in the row!</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfxISQdbrukIbPckrtxNLTQ-hZT_mYLdhLWeMXYkd7sR10WdFsBegFdqkJYBI-rfEPr0cWAEB9ur06Wke0xGLkROlJ5x6ml-nGEd73oyBT6WrV-sTO3wF75jzoB03rVPpO2RCjF69NT671/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="223" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfxISQdbrukIbPckrtxNLTQ-hZT_mYLdhLWeMXYkd7sR10WdFsBegFdqkJYBI-rfEPr0cWAEB9ur06Wke0xGLkROlJ5x6ml-nGEd73oyBT6WrV-sTO3wF75jzoB03rVPpO2RCjF69NT671/s16000/image.png" /></a></div><br /><br /><p></p><br /><br /><p></p><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div>You still have your original column of data but you can delete those values and start doing some form of row column comparison!</div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhu9OnJKVU_5mXVPvAEYkx2SqoO2we0oCawN-ea75eeqychxTmNSgyxd7UarhPyY7OIshhDozwAPD5BUdM5FsnLKMQc5xluzjuODpU2btr6PfnsFp2H5SvzB3EfosIy3c9lKm_3DINKWZ3i/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="222" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhu9OnJKVU_5mXVPvAEYkx2SqoO2we0oCawN-ea75eeqychxTmNSgyxd7UarhPyY7OIshhDozwAPD5BUdM5FsnLKMQc5xluzjuODpU2btr6PfnsFp2H5SvzB3EfosIy3c9lKm_3DINKWZ3i/s16000/image.png" /></a></div><br /><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div>...and so on!</div><div><br /></div><div>Note you can also transpose rows to columns. Comment below if you use this feature!</div>Geek Dadhttp://www.blogger.com/profile/12159682162763730420noreply@blogger.com0tag:blogger.com,1999:blog-9130450719881994773.post-90827879928638989312021-03-28T15:30:00.002+01:002021-03-28T15:36:44.758+01:00Excel Hack - Error Checking<p>A wise person once said "To err is human". Well Excel sometimes creates errors as well. Luckily there's a built-in way to check for errors and deal with them. A common error in computing is dividing by zero. Here's an example:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2NKZmuIxjWI-eiOVJD9CBXyeP0kiqdyx3ZTyhPfmbn0Iab74q4LR5rCYfi8fIuuW7CNwLANKr_YhYkigLI_b08W3y1NMc0up-X7nwQIqx3Gr1wtHOSEMIGGbzW7k-bpHWKi5576RPxJhZ/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="231" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj2NKZmuIxjWI-eiOVJD9CBXyeP0kiqdyx3ZTyhPfmbn0Iab74q4LR5rCYfi8fIuuW7CNwLANKr_YhYkigLI_b08W3y1NMc0up-X7nwQIqx3Gr1wtHOSEMIGGbzW7k-bpHWKi5576RPxJhZ/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>Cell C2 shows the formula that's been used. The "/" part of the formula is how you do divide in Excel and so "=A2/B2" means "take the value in cell A2 and divide it by the value in cell B2". </p><p>You can see how in cell B5 there is the number 0. In maths (or math for our American friends), a divide by zero is either called infinity or "undefined". Either way, it can't be computed. Hence Excel puts an error in a cell where you do a divide by zero, in this case "#DIV/0!".</p><p>You can test for a divide by zero using the "ISERROR" formula. Here's an example:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHsoAU5HgZXbkw159s6jlUw88BOi8_iCrlTWC3ZjNJUULhm9k0RNumafH9Fhyv6XmSBpM8w1LdM8TQJkRb6K9FHG_Tm08k_9JWUxRv7SJPGsZZm9rR1BXv9IMxuTlyG6FRhNiZ5UOdsscQ/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="227" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHsoAU5HgZXbkw159s6jlUw88BOi8_iCrlTWC3ZjNJUULhm9k0RNumafH9Fhyv6XmSBpM8w1LdM8TQJkRb6K9FHG_Tm08k_9JWUxRv7SJPGsZZm9rR1BXv9IMxuTlyG6FRhNiZ5UOdsscQ/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>So the formula "=ISERROR(C2)" simply means "look at the value in cell C2, if it's an error print TRUE, if it's not an error print FALSE".</p><p>You can then use an IF formula (see <a href="https://pdwhomeautomation.blogspot.com/2021/03/excel-basics-if-formula.html" target="_blank">here</a> for an explanation) to test for the error like this:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi7DV7eolnz7guWbZ5vSDMPAI7C-oUnmFl1aouzSBgmDm-50OKTNtTUncATZCWzZ6DXjyASP9teR1TycAQjlGqJPFE8X_W8jqgrVB3MElkVSWng67mUB_88n0sM-HKeplVWFAWpwcRIXeHS/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="209" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi7DV7eolnz7guWbZ5vSDMPAI7C-oUnmFl1aouzSBgmDm-50OKTNtTUncATZCWzZ6DXjyASP9teR1TycAQjlGqJPFE8X_W8jqgrVB3MElkVSWng67mUB_88n0sM-HKeplVWFAWpwcRIXeHS/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>So the formula above says "look at the value in cell D2. If it's TRUE print a blank cell, otherwise print the result of the division that was in cell C2.</p><p>In the above image I've applied the formula to row 5 where we have the divide by zero and it's come out as blank. What to set the cell to where there is an error is a design choice. For example:</p><p></p><ul style="text-align: left;"><li>Where there is a divide by zero error you could set the result cell to 0. However if you're also computing something like an average of the column overall you will get a skewed result. (The average of 3, 3, 9 and 0 is 5.25, the average of 3, 3, 9 and blank is 7).</li><li>You could set the cell to something like "ERROR - FIX ME" then you can search and fix these errors.</li></ul><p></p><p>What I've shown in the image above is something I often do in Excel to make things more maintainable. Specifically there is one formula per column (so divide in column C, error check in column D, IF statement in column E). </p><p>You can also chain together more than one formula in a cell like this:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfh56tCQ_N8PqL12kfws3dORK158RllhFJa0LEy2jzf6q7xu0cSQEVZFeIK2R3opcXtFBcbJHTT_DK_5Qy_H-Gm0w-oZ9DBT8pwutTQMGyYu-AgNzkSFFQCucghnVfgdvzwoSizshG3uEq/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="176" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhfh56tCQ_N8PqL12kfws3dORK158RllhFJa0LEy2jzf6q7xu0cSQEVZFeIK2R3opcXtFBcbJHTT_DK_5Qy_H-Gm0w-oZ9DBT8pwutTQMGyYu-AgNzkSFFQCucghnVfgdvzwoSizshG3uEq/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>In the above formula in column F we have a error check inside an IF statement. The way to interpret the formula is:</p><p></p><ul style="text-align: left;"><li>So "ISERROR(A2/B2)" to first check if the result is an error.</li><li>If it is an error, make the cell blank.</li><li>If it is not an error, compute A2 / B2 and put it in the cell.</li></ul><p></p><p>It's up to you how you choose to work. You can use multiple columns to build up the logic one column at a time or do one big multiple formula entry in a single cell. I choose the multiple columns option as it makes it easier weeks or months later when you come back to a spreadsheet and try and work out what you actually did!</p><p>There's also an "IFERROR" formula that does what I've done above in a single formula. Additionally there's a bunch of other errors you can check for. Have a play, see what you can come up with and comment below!</p><p><br /></p><p><br /></p>Geek Dadhttp://www.blogger.com/profile/12159682162763730420noreply@blogger.com1tag:blogger.com,1999:blog-9130450719881994773.post-80528211058342392972021-03-28T10:43:00.001+01:002021-03-28T15:24:42.920+01:00Excel basics - The IF Formula<p>Occasionally when using Excel you want to analyse the value in a cell and either calculate something differently as a result of the analysis or write something to a cell. To do this, your friend is the "IF formula". </p><p>Here's an example where I want to analyse the values in column A and write "TRUE" or "FALSE" to column B depending on whether the column A number is positive or negative. In the image below I show some simple data I've setup in column A and where I've started typing the IF formula in column B to show the different elements of the formula:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh93JeFaMsVQybXDYgDXS8zf1-d4GOuWrR4gbCl0Rrv99q8LWRPaRROqDCO18ga0Lu-cX56x4uIURugkw6NfJQxzGXPD9Hy2KUcez7-TmRMk2_gx4q1FeZKMAxlSioKN9qxS8g2HfCMd4SB/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="242" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh93JeFaMsVQybXDYgDXS8zf1-d4GOuWrR4gbCl0Rrv99q8LWRPaRROqDCO18ga0Lu-cX56x4uIURugkw6NfJQxzGXPD9Hy2KUcez7-TmRMk2_gx4q1FeZKMAxlSioKN9qxS8g2HfCMd4SB/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>Taking each element of the formula in turn:</p><p></p><ul style="text-align: left;"><li><b>logical_test</b> - This is the test you're doing on the values in column A. So for this formula I'll write "A2>0" meaning "test if A2 is greater than zero".</li><li><b>value_if_true</b> - This is what to put in column B if the logical_test is true.</li><li><b>value_if_false</b> - This is what to put in column B if the logical_test is false.</li></ul><p></p><p>Filling out the first formula I now have:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9un-IYGr1sVD03B6KhT6Cvx0FUvO7xPL3lRT3eoF_lP30UU0JrUflAvsx5J8dCUltv-OuG9qHW2oSXmYfhUDdU3SnI8ZGAtcgGcLco0RIQ6WZchdVtZM5UENeflHM1gBxXV_YJu3zQ9JE/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="310" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9un-IYGr1sVD03B6KhT6Cvx0FUvO7xPL3lRT3eoF_lP30UU0JrUflAvsx5J8dCUltv-OuG9qHW2oSXmYfhUDdU3SnI8ZGAtcgGcLco0RIQ6WZchdVtZM5UENeflHM1gBxXV_YJu3zQ9JE/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>...and copying the formula to the rest of column B I see:</p><p></p><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgGsBOZLVrZnQfw1olBW5K5TK-EKxsr1EOUL5OxyAriHWSVMI23lAmPD5nnu7SNlgYjGLP2-KuHPzBtTHQW8g4JQGgFgqKtZ7IGJws3bPEb5YhqCAQ-Y1G3j9mIY4cuvj2A0Z2KVTMz5row/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="315" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgGsBOZLVrZnQfw1olBW5K5TK-EKxsr1EOUL5OxyAriHWSVMI23lAmPD5nnu7SNlgYjGLP2-KuHPzBtTHQW8g4JQGgFgqKtZ7IGJws3bPEb5YhqCAQ-Y1G3j9mIY4cuvj2A0Z2KVTMz5row/s16000/image.png" /></a></div><br /><br /></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>Note how Excel has used it's clever <a href="https://pdwhomeautomation.blogspot.com/2021/03/excel-basics-using-sign-for-relative.html" target="_blank">relative references</a> capability to update the formula as it's copied down to refer to the correct row.</p><p>So that's it. Let me know in the comments below how you use the IF formula.</p>Geek Dadhttp://www.blogger.com/profile/12159682162763730420noreply@blogger.com0tag:blogger.com,1999:blog-9130450719881994773.post-15351105967598812372021-03-28T10:22:00.002+01:002021-03-28T10:22:45.997+01:00Excel Hack - Customising the Ribbon<p>The set of menus menu items across the top of the screen in Excel (and other Office Applications) is called the "Ribbon". It's the area at the top of the screen that has a set of easy to access menu items for you to use. Here's a snapshot of the ribbon with key parts picked out:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4rxBErFItUMmTQlqtcVy0xMEacG0r3hS64V3_TUe3LKNIsyoZBCngFLUCKzAfvsd5n7RdXkoQlcM1wntiPLohJNoJsmmZnkO24btld2B-0XFfFgW25EtPMzsTFh3QdB_9B9QyLPh7pQNj/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="313" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4rxBErFItUMmTQlqtcVy0xMEacG0r3hS64V3_TUe3LKNIsyoZBCngFLUCKzAfvsd5n7RdXkoQlcM1wntiPLohJNoJsmmZnkO24btld2B-0XFfFgW25EtPMzsTFh3QdB_9B9QyLPh7pQNj/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>Here you can see:</p><p></p><ul style="text-align: left;"><li>A set of <b>Tabs</b> (Home, Insert, Data etc) which is top level grouping for the ribbon.</li><li>A set of <b>Groups</b> (Clipboard, Font, Alignment etc) which sub-divide the tabs</li><li><b>Commands</b> (Bold, Italics, Underline etc) which are contained within the groups. Usually a subset of commonly used things associated with a group.</li><li>A small downward pointing arrow that when clicked opens the main menu for that Group. So in this example it will open the main Font menu with lots of extra controls over and above those shown in the ribbon group.</li></ul><p></p><p>I commonly use the strikethrough command (<strike>which makes text look like this</strike>) but it's not in the Font group on the ribbon. Hence I need to customise the ribbon. I start this by right clicking anywhere on the ribbon and selecting "Customize the Ribbon...". This brings up the form shown below. It's quite a busy form so I've circled a key control you'll need to use:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCvIBlr9BqwLoUjBve1OE5Hl8MfPoCObjXzJNo7PsCPdfg4T0f8YmtmndSkmce4SZNJkb6o6kl06jHFvTs9y525cafxDnW6Hl4k1ppFvQ8dFftQcVUM5_ZOhnB-Xm1C-1hgXarU4ofNoQU/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="494" data-original-width="602" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiCvIBlr9BqwLoUjBve1OE5Hl8MfPoCObjXzJNo7PsCPdfg4T0f8YmtmndSkmce4SZNJkb6o6kl06jHFvTs9y525cafxDnW6Hl4k1ppFvQ8dFftQcVUM5_ZOhnB-Xm1C-1hgXarU4ofNoQU/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>I want to create new Group within the Home tab so I simply click the "New Group" button, and a new Group called "New Group (Custom)" appears under "Ideas" in the "Home" tab. I right click on this, select "Rename", call the new group "Geek Dad", ignore where I can select an image for the group and click "OK". I then click on my "Geek Dad" group and drag it up under the Font group. </p><p>The form looks like this with my new group circled in blue and another area of the form we'll be dealing with circled in red:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDLQB3ZeEw1r_r97iPXY_YJ0HTeqNAvMtltCpj3E88fM5KAZ4t-UXdQczMYYghXe84ErEDzgug8UiJO_Ac8VPIhqFJ-MSlUNSB0hoDRQWB6bkEtf6kcWmbUuLqyYD7Eyzdtq0WWon-TAH7/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="392" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgDLQB3ZeEw1r_r97iPXY_YJ0HTeqNAvMtltCpj3E88fM5KAZ4t-UXdQczMYYghXe84ErEDzgug8UiJO_Ac8VPIhqFJ-MSlUNSB0hoDRQWB6bkEtf6kcWmbUuLqyYD7Eyzdtq0WWon-TAH7/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>You then click on what I've circled in red and change it to "Commands not in the ribbon". Scroll down, find the command you want, (in my case strikethrough), click on it and grab it to your new group. The form now looks like this:</p><p><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhoGhYGJOyHRILMS76RQKy3-MaEErvrStcnJ-v3-bWsZaFx0k-2fVmD3fdRU5kr2z8P_GdHTxtSYkhrrVN185OtRzELO1tyfocIYnpQs8EN6jNTNLtS5F1Y2OfIjzkRfCbFCpvgpDaWye-H/" style="clear: left; display: inline !important; margin-bottom: 1em; margin-right: 1em; text-align: center;"><img alt="" data-original-height="395" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhoGhYGJOyHRILMS76RQKy3-MaEErvrStcnJ-v3-bWsZaFx0k-2fVmD3fdRU5kr2z8P_GdHTxtSYkhrrVN185OtRzELO1tyfocIYnpQs8EN6jNTNLtS5F1Y2OfIjzkRfCbFCpvgpDaWye-H/s16000/image.png" /></a></p><p>...and the ribbon looks like this:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiP7Z-TfjwFWwtc2DFZ60PZUq6DTW-WXREs8PGiO9_u1jkIchgJ7_ICrqCqeJz9yN2LeRBBuymnDSERkuoZbzUaLbtRr7cdLC69G3liwQLButXsbcT5LoLpl2YsRj9obcUS4AU561lnpKIE/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="125" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiP7Z-TfjwFWwtc2DFZ60PZUq6DTW-WXREs8PGiO9_u1jkIchgJ7_ICrqCqeJz9yN2LeRBBuymnDSERkuoZbzUaLbtRr7cdLC69G3liwQLButXsbcT5LoLpl2YsRj9obcUS4AU561lnpKIE/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p>So go ahead; customise the ribbon to your hearts content for quick access to the controls you frequently use! Let me know what you get up to in the comments below.<br /><br /></p>Geek Dadhttp://www.blogger.com/profile/12159682162763730420noreply@blogger.com0tag:blogger.com,1999:blog-9130450719881994773.post-91601510597836037452021-03-27T16:40:00.000+00:002021-03-27T16:40:03.167+00:00The Curious Power of the PowerPoint Morph Transition<p> I've made many a PowerPoint presentation in my time and one feature that always seems to have a wow factor is the Morph Transition. </p><p>Here's a super-simple example in action:</p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxFGya51GxrUhIF9qRK3_g9UHPcoTATtOB3fJsXYmpQI_N3L-9U3a70lX8kaZ66EXWM8ZsHQxKDQD0c0XAyUtYkiFLuCKbaQ6BtXbJofVko2CImEtARP4Nl5vfGBB6bcRQHqj5UoQ-L3BK/s1920/morph_1a.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" data-original-height="1080" data-original-width="1920" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgxFGya51GxrUhIF9qRK3_g9UHPcoTATtOB3fJsXYmpQI_N3L-9U3a70lX8kaZ66EXWM8ZsHQxKDQD0c0XAyUtYkiFLuCKbaQ6BtXbJofVko2CImEtARP4Nl5vfGBB6bcRQHqj5UoQ-L3BK/w400-h225/morph_1a.gif" width="400" /></a></div><br /><p><br /></p><p>People say "Wow, you got objects to fly around the screen, that must be super complex to do". Not so; I shall explain how.</p><p>So how do you do this? Super, super easy. First prepare your "base" slide, the slide that other slides will transition from. Here's a simple example!</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh819panUJ9A_mgGxA1QAKy8RaXAAGTYVwwmOIgcULSmSEZWdM6D_l9pAzB_gnu9ZolJTte_TP9w_fEs5e8ftixdpDqvXxJ-HWHziSM-_4tuvadDPb8o7QdwhzCNmHJaNE4wjTeY3cjTpHn/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="322" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh819panUJ9A_mgGxA1QAKy8RaXAAGTYVwwmOIgcULSmSEZWdM6D_l9pAzB_gnu9ZolJTte_TP9w_fEs5e8ftixdpDqvXxJ-HWHziSM-_4tuvadDPb8o7QdwhzCNmHJaNE4wjTeY3cjTpHn/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>With this slide selected on the slide picker on the left, select the "Transitions" menu and the "Morph" transition as shown below.</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIdUbsOH0OMPYmYMzdGpaAPfK-zu8c29JdkP86xOeNR_59LDlKJe5hZh6R54SYy6gDO9ugHhWkysPxs08Bmh40xfZ9rkN8juFiLgloFpbqiRbtvj95LcCWPfVjdJ8EYEFep6upRD9wEAZi/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="316" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgIdUbsOH0OMPYmYMzdGpaAPfK-zu8c29JdkP86xOeNR_59LDlKJe5hZh6R54SYy6gDO9ugHhWkysPxs08Bmh40xfZ9rkN8juFiLgloFpbqiRbtvj95LcCWPfVjdJ8EYEFep6upRD9wEAZi/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>Right click on the slide in the slide picker on the left and select "Duplicate Slide" to create a second copy of the slide:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipoHrxy86fAKQpaR44hqKFwoaAXCzvd_yakrBWaBFwPN078zp8ekt5zFERTCHta0Lh2ZSd3YOoAaKgBCy2Ja1-X00nW1-zvEp_c6j2iypf21I4PEYShDlR6fizyf9KsUZKs_JazYSwM-XL/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="252" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipoHrxy86fAKQpaR44hqKFwoaAXCzvd_yakrBWaBFwPN078zp8ekt5zFERTCHta0Lh2ZSd3YOoAaKgBCy2Ja1-X00nW1-zvEp_c6j2iypf21I4PEYShDlR6fizyf9KsUZKs_JazYSwM-XL/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>Now edit this second slide by moving and re-sizing the drawing objects:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtS2B7L71Elzc0y5yn3fS_jVVATY2det4MR2-RhonTz19XqTaVfqzdNNiSC8V4rlJydy0Gz16Sk34Rz2UEOG3fhxS8OTyGsWGKd0i0mJ6ORoeeHDVKRUJF19RO3q4Ee8sZFQ6NLWDvuwlq/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="321" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtS2B7L71Elzc0y5yn3fS_jVVATY2det4MR2-RhonTz19XqTaVfqzdNNiSC8V4rlJydy0Gz16Sk34Rz2UEOG3fhxS8OTyGsWGKd0i0mJ6ORoeeHDVKRUJF19RO3q4Ee8sZFQ6NLWDvuwlq/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>Put the presentation in slideshow mode, go from the base slide to the next slide and you can see how PowerPoint automatically animates the transition.</p><p>This has been an example using made up PowerPoint objects (and ugly ones at that!). However in my working life I often use this Morph technique to tell a story during technical presentations. </p><p>Say I want to describe a computer system with 4 sub-systems. I would:</p><p></p><ul style="text-align: left;"><li>Prepare a slide with sub-system 1 on it. The slide has graphic for sub-system 1 and a text description.</li><li>I add a Morph Transition to slide 1 and duplicate it.</li><li>On slide 2 I shrink sub-system 1, show sub-system 2, show how sub-system 1 connects to sub-system 2 and describe sub-system 2.</li><li>Repeat for sub-system 3 on slide 3 and sub-system 4 on slide 4.</li><li>Create slide 5 with all the systems together and a final message.</li></ul><p></p><p>Real easy but has a big wow-factor.</p><p><br /></p>Geek Dadhttp://www.blogger.com/profile/12159682162763730420noreply@blogger.com0tag:blogger.com,1999:blog-9130450719881994773.post-56872473190706959452021-03-27T13:58:00.002+00:002021-03-27T13:58:15.221+00:00Excel Hack - Lookup Tables Using the vlookup Formula<p>Here's an Excel hack that I use all the time: lookup tables.</p><p>In <a href="https://pdwhomeautomation.blogspot.com/2021/03/excel-hack-dropdown-menu-in-cell.html" target="_blank">this previous post</a> I showed how to use Data Validation to create a dropdown list in a cell on a worksheet where I log my fruit consumption. Imagine I've been logging for a few weeks and have a spreadsheet with dates on it and what fruit I ate on that date. I now want to log the number of calories each fruit has. So my Fruit Worksheet looks like this:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEituEUKS1AY5-41oS-Sy4wjKpZhSl4iaZrLPsUwbpfYqgFVWQj0_d_YNLmspLGOqkvcKdvxBdNB9m1h86z1RO9I10T1boghpMZ13uOLZtAYdE9CXG-Ro_cDfbj7dBlLUlqnxJrqDNgUs6Us/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="421" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEituEUKS1AY5-41oS-Sy4wjKpZhSl4iaZrLPsUwbpfYqgFVWQj0_d_YNLmspLGOqkvcKdvxBdNB9m1h86z1RO9I10T1boghpMZ13uOLZtAYdE9CXG-Ro_cDfbj7dBlLUlqnxJrqDNgUs6Us/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>Now I could lookup the calorie value of each on the internet and type it into each relevant cell. But that would be a pain on this sheet as there are repetitions of the same fruit (for example 3 oranges) and will be a real pain as I enter more and more fruit. Instead I setup a lookup table on my Lookup worksheet; it looks like this:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZNH4Wn0yII_-VBlb9F8CmbEVbD9_1Gqw4iASJLkDj_byGz2F5v79DJcQp0QqHPJ6le7CqRCZ_-DwpKxOzGbTfw6fHrjtT3EK6LS7ncwvDFL6nXr7q0c0GqWzfTwqICHRWCJxviT6pNIHm/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="566" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjZNH4Wn0yII_-VBlb9F8CmbEVbD9_1Gqw4iASJLkDj_byGz2F5v79DJcQp0QqHPJ6le7CqRCZ_-DwpKxOzGbTfw6fHrjtT3EK6LS7ncwvDFL6nXr7q0c0GqWzfTwqICHRWCJxviT6pNIHm/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>I then go back to cell C2 (the first calories cell) on my Fruit Worksheet to enter my lookup formula. I start by typing "=vlookup(" in the cell and I see what's shown below. Here Excel shows you all the different elements of the formula.</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUuhL1lSVk4Qryfn6bfBa7-M1Y3DyuMfjkpzGh44AbGCh4viOhA8YKRaeEkanw27vCuhXsPJczLQoHXQD8rnBKZMy1nZJRtIloU0-Xh8eenZBstAYb7hB2TuzaqvleN2lKks-AMZVCAgT7/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="247" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjUuhL1lSVk4Qryfn6bfBa7-M1Y3DyuMfjkpzGh44AbGCh4viOhA8YKRaeEkanw27vCuhXsPJczLQoHXQD8rnBKZMy1nZJRtIloU0-Xh8eenZBstAYb7hB2TuzaqvleN2lKks-AMZVCAgT7/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>Oh my! Looks a bit complex. Let's enter a real life example and then break it down:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgo8jS-tBbmEyYKhtyXNk16eXL2UBg0MfVo3g5g-u7W-Gv7VSky4pfKTXejXglR3uE1EWKW8K2klWXo-i8HByaBX3qf5250mXGunp7Zbl214rWYQvp9sdZeHe0sDlGmLzRl0O1HXIuKX0m_/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="244" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgo8jS-tBbmEyYKhtyXNk16eXL2UBg0MfVo3g5g-u7W-Gv7VSky4pfKTXejXglR3uE1EWKW8K2klWXo-i8HByaBX3qf5250mXGunp7Zbl214rWYQvp9sdZeHe0sDlGmLzRl0O1HXIuKX0m_/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>So let's match the Excel formula element to what I entered:</p><p></p><ul style="text-align: left;"><li>lookup_value becomes <i>B2</i>. Meaning you're looking up the value from cell B2 (so Kiwi) in the lookup table.</li><li>table_array becomes <i>Lookup!A2:B8</i> which means our calories lookup table is on a worksheet named "Lookup" in the cell range A2 to B8. This uses the "!" method to refer to values on a different worksheet, as explained <a href="https://pdwhomeautomation.blogspot.com/2021/03/excel-basics-referring-to-cells-on.html" target="_blank">here</a>. </li><li>col_index becomes <i>2</i>. This means, when you've found lookup_value in the first column of the lookup table, you can find the value you're looking up in the second column. This is useful if you have a lookup table with more than one value to lookup. e.g. We could have a third column in the lookup table with colour in it and would use 3 as the col_index.</li><li>range_lookup becomes <i>False</i>. This means don't do an approximate match on the lookup_value, do an exact match. I always use False to make sure there is an exact match. I guess you could use True (to get an approximate match) if you were unsure whether the spelling on the target and lookup tables was an exact match.</li></ul><p></p><p>When I hit return to run the formula you can see the lookup has worked:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWHzIoFZ0rnaJ7A5zdU8bRC-G87sqzJtanwyYuS9zqwbqwcaklDVq6DQn2F4FjZF55TRQeke8Fhmdd4iBhyphenhyphenRB_Lo3oFYkYkHxtIITZfu0M4pWgR8WHDpY8IqJtxvCorp2dBN1GkzaJ_cGt/" style="clear: left; display: inline !important; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="364" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiWHzIoFZ0rnaJ7A5zdU8bRC-G87sqzJtanwyYuS9zqwbqwcaklDVq6DQn2F4FjZF55TRQeke8Fhmdd4iBhyphenhyphenRB_Lo3oFYkYkHxtIITZfu0M4pWgR8WHDpY8IqJtxvCorp2dBN1GkzaJ_cGt/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>I then tweak the formula to make the lookup table an absolute reference (see <a href="https://pdwhomeautomation.blogspot.com/2021/03/excel-basics-using-sign-for-relative.html" target="_blank">here</a> for an explanation). Here the lookup table cell reference becomes<i> $A$2:$B$8</i> which means wherever I copy the vlookup formula to on the worksheet, it always refers to the exact same lookup table.</p><p>Top tip: To do this I select cell C2, go to the formula bar, select the cell range and press "F4". This automatically put the $ signs in. You can see this below:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHC05p19wGcrdxWDAvosNeGMTS_DnjmsmYdfhVVKFcWpoBaMXVXdTCwmjga2J_KtPA3J8gSK8zKBMMpb0VqwIb9PgEBS5WL6sTxgIy_tCGv3G7Kx66st7RbMJuIYeQd0F-i_ycnHYn2s_G/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="226" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhHC05p19wGcrdxWDAvosNeGMTS_DnjmsmYdfhVVKFcWpoBaMXVXdTCwmjga2J_KtPA3J8gSK8zKBMMpb0VqwIb9PgEBS5WL6sTxgIy_tCGv3G7Kx66st7RbMJuIYeQd0F-i_ycnHYn2s_G/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>With my absolute reference in place I can copy my formula down to all the other Calories column cells and the lookup works just fine!</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1xxQN96fSR63kBedjQq-28gy6gTdY8FXOWhxhTRyKv2jTBMdSYhDFUR46rtBuTw-N5xzJ9tnN0U0_u8v81lXk2uHsLRkDs82XMLnP7KwHLIXz0lNNYnDi7WJUW4FthniOtIodYIz2aJCH/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="363" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1xxQN96fSR63kBedjQq-28gy6gTdY8FXOWhxhTRyKv2jTBMdSYhDFUR46rtBuTw-N5xzJ9tnN0U0_u8v81lXk2uHsLRkDs82XMLnP7KwHLIXz0lNNYnDi7WJUW4FthniOtIodYIz2aJCH/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>So that's it for vlookup. There is also a hlookup formula for where you have a lookup table with your lookup_value in a single row and the values you're looking up below it. I've never used hlookup as a lookup table such as that shown above just seems "right"!!</p>Geek Dadhttp://www.blogger.com/profile/12159682162763730420noreply@blogger.com0tag:blogger.com,1999:blog-9130450719881994773.post-74789098975806711142021-03-27T13:28:00.000+00:002021-03-27T13:28:09.718+00:00Excel Basics - Referring to Cells on Different Worksheets<p>Sometimes in Excel, you're working on one worksheet and want to refer to a value in a cell on a different worksheet. By way of example, in <a href="https://pdwhomeautomation.blogspot.com/2021/03/excel-basics-using-sign-for-relative.html" target="_blank">this post</a> I described using a spreadsheet with an exchange rate on it to convert from home currency to local currency when I am abroad. Here's a quick image to remind you:</p><p></p><div class="separator" style="clear: both; text-align: left;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjc0PRf-CuhDdtZFTUelnE9bwWWm4v544x7Frwv5UgLAfL2yBmStv2aCnprLR2lIi0A7byjjL6FwvMepJ0zspx3haRP29WJTsI1Eia7A2cia_0hJ2OSp7-zdlCsLL4jB7sF1-4lWgsKfzqC/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="314" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjc0PRf-CuhDdtZFTUelnE9bwWWm4v544x7Frwv5UgLAfL2yBmStv2aCnprLR2lIi0A7byjjL6FwvMepJ0zspx3haRP29WJTsI1Eia7A2cia_0hJ2OSp7-zdlCsLL4jB7sF1-4lWgsKfzqC/s16000/image.png" /></a></div><br />Let's say I'm abroad but on business, not on holiday and I want to keep track of personal and business spending on different worksheets. I'd use the worksheet above for personal expenditure and call it "Holiday Spends" and also setup another one like the one below for business expenditure. Note I've not put the exchange rate on the worksheet because I want to use the one on the "Holiday Spends" worksheet.<p></p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwnIn9dHiTc6ma5PVRVLvBRLsHwk8GFRfqbJyv78VImy_-j7ArukfZqRHmjCavw0rSPm808AtGoZ15wU9ZdMyx71ODZVYu-fdF3X-JDFr8X3bWM8GtmiMNPfiOkXzDw6iUoQZJQ8r7RgIG/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="310" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiwnIn9dHiTc6ma5PVRVLvBRLsHwk8GFRfqbJyv78VImy_-j7ArukfZqRHmjCavw0rSPm808AtGoZ15wU9ZdMyx71ODZVYu-fdF3X-JDFr8X3bWM8GtmiMNPfiOkXzDw6iUoQZJQ8r7RgIG/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><br /><br /><p></p><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div>A formula to use the exchange rate from the "Holiday Spends" sheet looks like this:</div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijdYA75kDT2zwUyUxLba4zeGZuVIxZ682cZ9gEZv3yEsrQlsmrAUElDbobQThm70bIUFw1KwjQkeBXevvgZ7PKebdu6MkffaQmPe-CMbRc5dVij8dOUH9P2kenzbPv5xYOySNxTJrpvEB4/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="263" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEijdYA75kDT2zwUyUxLba4zeGZuVIxZ682cZ9gEZv3yEsrQlsmrAUElDbobQThm70bIUFw1KwjQkeBXevvgZ7PKebdu6MkffaQmPe-CMbRc5dVij8dOUH9P2kenzbPv5xYOySNxTJrpvEB4/s16000/image.png" /></a></div><br /><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div>So what can we see here?</div><div><ul style="text-align: left;"><li>The <i>C9</i> means "use the value in cell C9 on <b>this</b> worksheet.</li><li>The <i>*</i> means multiply</li><li>The <i>'Holiday Spends'!$A$2</i> means use cell A2 from worksheet "Holiday Spends". Remember the worksheet name goes in single quotes and there's an exclamation mark between the worksheet name and the cell reference.</li></ul></div><div>Note that you don't have to type that formula out yourself. You can build it up by:</div><div><ul style="text-align: left;"><li>Clicking in the target cell for the formula (so cell D9 in the above example).</li><li>Typing the "=" sign</li><li>Clicking on cell C9 of the current worksheet</li><li>Typing the "*" sign</li><li>Switching to the worksheet with the cell on it you want to refer to and clicking on the cell.</li><li>Pressing the return key</li><li>If required, editing the formula to make the cell on the other worksheet an <a href="https://pdwhomeautomation.blogspot.com/2021/03/excel-basics-using-sign-for-relative.html" target="_blank">absolute reference</a>.</li></ul></div><div><br /></div><div>Hope that's clear!</div><div><br /></div><div><br /></div>Geek Dadhttp://www.blogger.com/profile/12159682162763730420noreply@blogger.com0tag:blogger.com,1999:blog-9130450719881994773.post-15483094723477923562021-03-27T12:59:00.001+00:002021-03-27T13:04:21.440+00:00Excel Basics - Using the $ Sign for Relative and Absolute References (or "Sticking" to Cells)<p>When using Excel, you often write a formula in a cell that refers to another cell. Here's a simple example:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZ2_y6ZerylpXpBtB6LwmvCCQl2GaRXCtYRi60SBH8-UU87TYgbKDCgDedp1IX0-84BnMKHOaROWpBjm8tnZBu82gXFo7tOCI6p907FqxRcyWDCYL2-2wTjeEI9izIfQEF_rIZSQ4Y9tkD/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="427" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiZ2_y6ZerylpXpBtB6LwmvCCQl2GaRXCtYRi60SBH8-UU87TYgbKDCgDedp1IX0-84BnMKHOaROWpBjm8tnZBu82gXFo7tOCI6p907FqxRcyWDCYL2-2wTjeEI9izIfQEF_rIZSQ4Y9tkD/s16000/image.png" /></a></div><br /><br /><p></p><p></p><div class="separator" style="clear: both; text-align: center;"><br /></div><p style="text-align: left;">It's pretty easy to understand:</p><p style="text-align: left;"></p><ul style="text-align: left;"><li>There is a value in column A, row 1 and that is cell A1</li><li>There is a value in column B, row 1 and that is cell B1</li><li>There is a formula in column C, row 1 (cell C1) which is "=A1+B1" which means "add the value in cell A1 to the value in cell B1".</li></ul><p></p><p>When I click away from cell C1, the sum of cells A1 and B1 is shown. Easy!</p><p>Say I want to add up more rows with values on columns A and B. First I enter all the values I want to add:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjX1lSTk6Le0TEUsmDjjL5rS_P5VDivfat1aqLrEPZ0p8aG9DivQzJJxqtdTFsUSNbIfAl1txK-_HaPGJN9v78JK1aukx5dpkOs4qOaZ9VWIHd-fYfeBHDcVV39JSP8Lz8YUx03svTngyUE/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="383" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjX1lSTk6Le0TEUsmDjjL5rS_P5VDivfat1aqLrEPZ0p8aG9DivQzJJxqtdTFsUSNbIfAl1txK-_HaPGJN9v78JK1aukx5dpkOs4qOaZ9VWIHd-fYfeBHDcVV39JSP8Lz8YUx03svTngyUE/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><em style="box-sizing: border-box; font-family: "Open Sans", sans-serif; font-size: 1.4em;"><br /></em></p><p><em style="box-sizing: border-box; font-family: "Open Sans", sans-serif; font-size: 1.4em;"><br /></em></p><p><em style="box-sizing: border-box; font-family: "Open Sans", sans-serif; font-size: 1.4em;"><br /></em></p><p><em style="box-sizing: border-box; font-family: "Open Sans", sans-serif; font-size: 1.4em;"><br /></em></p><p><em style="box-sizing: border-box; font-family: "Open Sans", sans-serif; font-size: 1.4em;"><br /></em></p><p><em style="box-sizing: border-box; font-family: "Open Sans", sans-serif; font-size: 1.4em;"><br /></em></p><p><em style="box-sizing: border-box; font-family: "Open Sans", sans-serif; font-size: 1.4em;"><br /></em></p><p><em style="box-sizing: border-box; font-family: "Open Sans", sans-serif; font-size: 1.4em;"><br /></em></p><p>Then I just copy the formula from cell C1 to cells C2 through to C9. This gives me:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2FjkmX0ZsLeKPCDr4V59kdtaOsbcqnG3739y5nthIwJ-65d5XjJv03Ufq6veKC1OL2MnfT904wwjylDZrYenjEsYGsuNcCIM8n7ahIO-ju_4AEV4k7OjXFGpiZCCq6EdXkjxqIeGBmg5c/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="394" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg2FjkmX0ZsLeKPCDr4V59kdtaOsbcqnG3739y5nthIwJ-65d5XjJv03Ufq6veKC1OL2MnfT904wwjylDZrYenjEsYGsuNcCIM8n7ahIO-ju_4AEV4k7OjXFGpiZCCq6EdXkjxqIeGBmg5c/s16000/image.png" /></a></div><br /><br /><p></p><p><em style="box-sizing: border-box; font-family: "Open Sans", sans-serif; font-size: 1.4em;"><br /></em></p><p><em style="box-sizing: border-box; font-family: "Open Sans", sans-serif; font-size: 1.4em;"><br /></em></p><p><em style="box-sizing: border-box; font-family: "Open Sans", sans-serif; font-size: 1.4em;"><br /></em></p><p><em style="box-sizing: border-box; font-family: "Open Sans", sans-serif; font-size: 1.4em;"><br /></em></p><p><em style="box-sizing: border-box; font-family: "Open Sans", sans-serif; font-size: 1.4em;"><br /></em></p><p><em style="box-sizing: border-box; font-family: "Open Sans", sans-serif; font-size: 1.4em;"><br /></em></p><p><em style="box-sizing: border-box; font-family: "Open Sans", sans-serif; font-size: 1.4em;"><br /></em></p><p><em style="box-sizing: border-box; font-family: "Open Sans", sans-serif; font-size: 1.4em;"><br /></em></p><p><em style="box-sizing: border-box; font-family: "Open Sans", sans-serif; font-size: 1.4em;"><br /></em></p><p>What you can see above is that when the formula is copied to each row, Excel keeps the column letters the same (so A and B) but changes the row numbers. So in the example above on row 9, it has copied the formula from cell C1 (which was "=A1+B1") and assumed you want to add up the values in row 9 and has made the formula ("=A9+B9"). </p><p>This is an example of Excel using relative references, i.e. it updates the formula relative to the row you copy the formula to. Note, if you copy a formula across different columns of the same row then Excel will modify the column letter part of the formula.</p><p>"This is obvious!" I hear you cry. But what if you want to copy a formula down rows (or across columns) and <b>not</b> have the row/column change. Here's where <b>absolute references </b>come in!!</p><p>Imagine you're on holiday abroad using a local currency and you want to keep track of how much you're spending in your home currency. You can type the exchange rate in one cell on the spreadsheet and use an absolute reference to use the exchange rate in every calculation. Here's an example:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgsRW3f8G11w8zcEXq3f1IdxFGQkrSSzyNqYpzqfBFxHTRdeOi3A2xVuMCUOtAi_bdgUjfgbZcZtXwnlVwIfO18gS-FgXVz6KtCO01cKr3eIFcXtZENTtvbZF0lGGYijCW9hhLrliIf8xNm/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="314" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgsRW3f8G11w8zcEXq3f1IdxFGQkrSSzyNqYpzqfBFxHTRdeOi3A2xVuMCUOtAi_bdgUjfgbZcZtXwnlVwIfO18gS-FgXVz6KtCO01cKr3eIFcXtZENTtvbZF0lGGYijCW9hhLrliIf8xNm/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>Cell A2 has the exchange rate. So every 1 unit of local currency equates to 2 units of home currency. I've logged all my local currency spends and in cell D5 have entered the formula to convert to home currency by multiplying by the exchange rate, (the * sign means multiply).</p><p>What can seen is that the formula is "=C5*$A$2". So what do we have here?</p><p></p><ul style="text-align: left;"><li>The C5 is a <b>relative reference</b>. When we copy the formula down to rows 6 through to 9, C5 becomes C6, becomes C7 and so on.</li><li>The $A$2 is an <b>absolute</b> <b>reference</b>. When we copy the formula down to rows 6 through to 9, the $ signs means "keep the reference to cell A2". We're effectively "sticking" the formula to cell A2. Here we can see it in action:</li></ul><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhj4fMTDsMBT7SM2AHRkTZUEbJ3inx8oPpPdG_Ws1YSqCn_bc79IA_4k7x43wTLBTj1CExvLx7z9PATH6AMtIbBVX4Jtr_sk5KO1g5OxRbHrLjAR0yxv8TdjrBtQo3ghF_zHLmLvMdX0XvB/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="315" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhj4fMTDsMBT7SM2AHRkTZUEbJ3inx8oPpPdG_Ws1YSqCn_bc79IA_4k7x43wTLBTj1CExvLx7z9PATH6AMtIbBVX4Jtr_sk5KO1g5OxRbHrLjAR0yxv8TdjrBtQo3ghF_zHLmLvMdX0XvB/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>As you can see the formula in cell D9 is "=C9*$A$2"; we've kept the link to cell A2, as can be seen in the calculation results in the cells above.</p><p></p><div class="separator" style="clear: both; text-align: left;">We can use these absolute and relative references in other ways. Here's some examples:</div><div class="separator" style="clear: both; text-align: left;"><ul style="text-align: left;"><li>=$A$1:$C$5 would be used in a formula that works across a number of rows and columns (so columns A to C and rows 1 to 5 in the example). As you copy the formula into other cells on the spreadsheet it always refers to this range.</li><li>=$A1 is a <b>mixed reference</b>, the A is absolute, the 1 is relative. This would mean that if you copy the formula across and down rows, the link to column A would remain but the link to row 1 would change.</li><li>You can also do the opposite of the above with something like =A$1, another mixed reference but one that anchors to row 1 and allows the column to change.</li></ul>Hope that was useful!</div><div class="separator" style="clear: both; text-align: left;"> </div><br /><br /><div class="separator" style="clear: both; text-align: center;"><br /></div><br /><p></p><p><br /></p><p><br /></p>Geek Dadhttp://www.blogger.com/profile/12159682162763730420noreply@blogger.com0tag:blogger.com,1999:blog-9130450719881994773.post-14402399823876531772021-03-21T17:33:00.005+00:002021-03-25T13:29:08.947+00:00Excel Hack - A Dropdown Menu in a Cell<p>Ever seen people use spreadsheets where they click on a cell and there's a drop down menu shown for them to select a value? It's a useful technique to make sure data is entered accurately into a spreadsheet.</p><p>Want to know how to do it? Read on! Here's how it looks when it's done:</p><p><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtBd1MUOIJUVQiF66q6sosdRASJWh-MWM7WhNhyEUEa8gEC61DlFHzauP0tyVlWIm-meFMf6BKZ1m0n4Jn6aBnzsBAzLqdNT0py9RncALbu3hubfe56hhZYZk6KFSD7if2isbzQxOKKFnf/" style="clear: left; margin-bottom: 1em; margin-right: 1em; text-align: center;"><img alt="" data-original-height="494" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtBd1MUOIJUVQiF66q6sosdRASJWh-MWM7WhNhyEUEa8gEC61DlFHzauP0tyVlWIm-meFMf6BKZ1m0n4Jn6aBnzsBAzLqdNT0py9RncALbu3hubfe56hhZYZk6KFSD7if2isbzQxOKKFnf/s16000/image.png" /></a></p><p>As an example to show how it's done, I'm going to create a spreadsheet to log my fruit consumption. I create a new worksbook called "fruit_logger.xlsx" and create two worksheets: "Fruit Worksheet" and "Lookup". See <a href="https://pdwhomeautomation.blogspot.com/2021/03/excel-basics-workbooks-files-and.html" target="_blank">this article</a> for guidance on worksbooks, worksheets and renaming tabs.</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhd9RXCgCFcySMBlZct8-3Ncqc8GXHIZYA2u3_CwSKuU7bY5zt1JVq_4ROZbaIhNhB-78c38Vk5EDSgHinlKs5n6v0YfmCkMjN3qeXPMCkseZkBue_2mOK1KH7_mMyi8rUmIoHXGTGDBKi7/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="384" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhd9RXCgCFcySMBlZct8-3Ncqc8GXHIZYA2u3_CwSKuU7bY5zt1JVq_4ROZbaIhNhB-78c38Vk5EDSgHinlKs5n6v0YfmCkMjN3qeXPMCkseZkBue_2mOK1KH7_mMyi8rUmIoHXGTGDBKi7/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>On my Lookup sheet I add all the fruits I'm ever likely to eat in a single column:</p><p></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-L08nBs2tjFOw7Z5ZiEsBVrxrwNxXDv7SdPVRmVlCSridYhWDCYGp-au3UbGEo4Dwhd5srNgxHSyyMZ3soe2XQfHkTLfEz9VWOY3GlWnuMPhEd3CERhW4-o_ZcRZ60rIQ5_O3eV4QGG8q/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="339" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-L08nBs2tjFOw7Z5ZiEsBVrxrwNxXDv7SdPVRmVlCSridYhWDCYGp-au3UbGEo4Dwhd5srNgxHSyyMZ3soe2XQfHkTLfEz9VWOY3GlWnuMPhEd3CERhW4-o_ZcRZ60rIQ5_O3eV4QGG8q/s16000/image.png" /></a></div><br /><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>I then go to my Fruit Worksheet and select cell A1. I select the "Data" tab at the top of the screen, then in the Data Tools group I select the "Data Validation" icon, the one with a green tick and a no entry sign on it. It's circled on the diagram below:</p><p></p><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBB1jbnGaz7TBC2lxE7JIi6yTzvrpokbmMo2jjHHz-9cXdQeIDLLpJECT5t1DE2Q-DCPpD68RbU7PYikNYD8PuXKrhM7VQccRqfm9TBIfx70wmpOT8CQ-OwNDF8s5mlfAsrjpl7nMNg99j/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="412" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjBB1jbnGaz7TBC2lxE7JIi6yTzvrpokbmMo2jjHHz-9cXdQeIDLLpJECT5t1DE2Q-DCPpD68RbU7PYikNYD8PuXKrhM7VQccRqfm9TBIfx70wmpOT8CQ-OwNDF8s5mlfAsrjpl7nMNg99j/s16000/image.png" /></a></div><br /><br /></div><br /><br /></div><br /><br /></div><br /><br /></div><br /><br /><p></p><p></p><div class="separator" style="clear: both; text-align: left;">After clicking Data Validation the form shown below appears. Under "Allow:" select "List". Then click the up arrow button to the right of the box under where it says "Source". It's circled on the diagram below:</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0MDMVs7b1IS5Zt4PV_fEfqSLoYTTZ1WS1GDKxNTrp_TkYWqZoklYjVCukhv-Hui-uuB2xgSEtDpnzDZq3NQWLC1Ef5hxtiB4SjAMkeOwjTxN3ryu9pi7yKyU0Bif-N6rVD7114zT0E2FQ/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="400" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi0MDMVs7b1IS5Zt4PV_fEfqSLoYTTZ1WS1GDKxNTrp_TkYWqZoklYjVCukhv-Hui-uuB2xgSEtDpnzDZq3NQWLC1Ef5hxtiB4SjAMkeOwjTxN3ryu9pi7yKyU0Bif-N6rVD7114zT0E2FQ/s16000/image.png" /></a></div><br /><br /></div><br /><br /></div><div class="separator" style="clear: both; text-align: left;">A small box with "Data Validation" at the top appears. Click on the "Lookup" worksheet, click on cell A1, keep the mouse button clicked and drag down until you've got all your fruit selected. The data validation form now looks like the image below. The "Source:" value of =Lookup!$A$1:$A$7 simply means, "look at cells A1 through to A7 on the worksheet called 'Lookup".</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhT_YUEaTrG1viGwfHeDzcTWfQ4ndxMvzast-UDvDtv0VPK3zu-mpCv1O1am0p4ZK-y0zvTAddM_WqoJ-BnvjgsX9xFgWeKnt1s1DEtcUef_mphpDq03-PejQUOxTHbxib53Noe2wHd-RSC/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="397" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhT_YUEaTrG1viGwfHeDzcTWfQ4ndxMvzast-UDvDtv0VPK3zu-mpCv1O1am0p4ZK-y0zvTAddM_WqoJ-BnvjgsX9xFgWeKnt1s1DEtcUef_mphpDq03-PejQUOxTHbxib53Noe2wHd-RSC/s16000/image.png" /></a></div><br /><br /></div><div class="separator" style="clear: both; text-align: left;">Click OK and you're back to your Fruit Worksheet. Click into cell A1 and you can see the drop down list of fruits!</div><div class="separator" style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtBd1MUOIJUVQiF66q6sosdRASJWh-MWM7WhNhyEUEa8gEC61DlFHzauP0tyVlWIm-meFMf6BKZ1m0n4Jn6aBnzsBAzLqdNT0py9RncALbu3hubfe56hhZYZk6KFSD7if2isbzQxOKKFnf/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="494" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhtBd1MUOIJUVQiF66q6sosdRASJWh-MWM7WhNhyEUEa8gEC61DlFHzauP0tyVlWIm-meFMf6BKZ1m0n4Jn6aBnzsBAzLqdNT0py9RncALbu3hubfe56hhZYZk6KFSD7if2isbzQxOKKFnf/s16000/image.png" /></a></div><br /><br /></div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">If you type anything that's not on the list into the cell you get this snazzy error message:</div><div class="separator" style="clear: both; text-align: left;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfkCJKS4Pz063XZHH2rIM8Kwxpok-DggfxlOWS8dTkKPU2CkLunUbL42Q7jluVg-zWB3b6jUltDA026T6ekyFGS7UmMcm1pcomlllgyA6dcl3XtVLBqmcW3PsadK7J6Cu6p5QK_Lo_FE3W/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="416" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfkCJKS4Pz063XZHH2rIM8Kwxpok-DggfxlOWS8dTkKPU2CkLunUbL42Q7jluVg-zWB3b6jUltDA026T6ekyFGS7UmMcm1pcomlllgyA6dcl3XtVLBqmcW3PsadK7J6Cu6p5QK_Lo_FE3W/s16000/image.png" /></a></div><br /><br /></div><div class="separator" style="clear: both; text-align: left;">To have this drop down in more cells, simply copy cell A1 and paste it into the other cells where you want the list!</div><div class="separator" style="clear: both; text-align: left;"><br /></div><div class="separator" style="clear: both; text-align: left;">Comment below if this worked OK for you!</div><div class="separator" style="clear: both; text-align: left;"><br /></div><br /><br /><p></p><p><br /></p>Geek Dadhttp://www.blogger.com/profile/12159682162763730420noreply@blogger.com0tag:blogger.com,1999:blog-9130450719881994773.post-54962384093464151232021-03-21T11:48:00.000+00:002021-03-21T11:48:16.248+00:00Excel Basics - Workbooks (Files) and Worksheets (Tabs)<p>This blog post used Excel from Office 365, version 2021. This is general stuff so won't change from version to version.</p><p>The purpose of this blog post is to tell you about workbooks and worksheets. A workbook is basically an Excel file. So a book where you do work. Get it!</p><p>When you start Excel you're presented with a screen like that shown below:</p><p></p><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhohi64s9RGR2OLyGKD5XNXEOoJhFvIFLZCFjnjtEmhR1RcgQRJT1_ogY0LZ1DPutqfQEwKzfOgNLA-dqEHGmM9AaDiQDh4DhzZsvWDLHwbEqFzneAtULvh2pHIPHc-MaN1SdssNK-G8ycd/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="219" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhohi64s9RGR2OLyGKD5XNXEOoJhFvIFLZCFjnjtEmhR1RcgQRJT1_ogY0LZ1DPutqfQEwKzfOgNLA-dqEHGmM9AaDiQDh4DhzZsvWDLHwbEqFzneAtULvh2pHIPHc-MaN1SdssNK-G8ycd/s16000/image.png" /></a></div><br /><br /></div><br /><br /></div><br /><br /></div><br /><br /></div><br /><br /><div><br /></div><div><br /></div><div><br /></div><div>Select "Blank workbook" and you're presented with a new Excel workbook to work in. <p></p><p>Select "File" and then "Save", find a location to save the file in and give it a name of your choice. I called mine "fruit_logger.xlsx". So now my workbook is good and ready!</p><p></p><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhzhnQZ_CITeISoNL_Po1uC3VZejswEbdA49_1pbE7__WEiW_m8aamjC7NIf9212lYfoY6DAmXbbpiglkk4X3pn02kpqNa6-WSguEytRI0XG2jmUiiBqsb5cukBzxaj512Z3e6EnBJaU2GA/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="299" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhzhnQZ_CITeISoNL_Po1uC3VZejswEbdA49_1pbE7__WEiW_m8aamjC7NIf9212lYfoY6DAmXbbpiglkk4X3pn02kpqNa6-WSguEytRI0XG2jmUiiBqsb5cukBzxaj512Z3e6EnBJaU2GA/s16000/image.png" /></a></div><br /><br /></div><br /><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div>Now it's time to talk about worksheets, or as they're sometimes called, "tabs". Think of worksheets as individual pages in your workbook. You can have different sets of data on each of your worksheets.<p></p><p>You select, edit, add and delete worksheets from the bottom of you workbook. Here we can see our default worksheet is called "Sheet1":</p><p></p><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEil4pGlb7zEfOGLd5ur3e3E56nS7bNK1-BQmTTvcgiCbNAA5kJb_yi5FsBYH5HjXM6B-3vL0-H-V9kceQzzbs6vFJrgA7L1vdKcidz_GC3azEPLMrYcImj2svWOdphOy2sgiZwvEnWLmw9S/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="595" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEil4pGlb7zEfOGLd5ur3e3E56nS7bNK1-BQmTTvcgiCbNAA5kJb_yi5FsBYH5HjXM6B-3vL0-H-V9kceQzzbs6vFJrgA7L1vdKcidz_GC3azEPLMrYcImj2svWOdphOy2sgiZwvEnWLmw9S/s16000/image.png" /></a></div><br /><br /></div><br /><br /></div><br /><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div>Right click where it says "Sheet1" to see what options you have. <p></p><p></p><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhECbCCPDM1SSrQUqKyR59H_i9RDVUOv6ESe1x2gDpvdMg_Rr06RwtkQLpniD63JUgWXDku-ubdIYSaui2ywWlZOUNSY3XwMyRL2YkzfZvlGELH_6uAikOlOua28YULok_fldo5X4bZoDrL/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="314" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhECbCCPDM1SSrQUqKyR59H_i9RDVUOv6ESe1x2gDpvdMg_Rr06RwtkQLpniD63JUgWXDku-ubdIYSaui2ywWlZOUNSY3XwMyRL2YkzfZvlGELH_6uAikOlOua28YULok_fldo5X4bZoDrL/s16000/image.png" /></a></div><br /><br /></div><br /><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div>First I'll rename it by selecting the "Rename" option. This turns the worksheet name grey and allows you to delete the old name and type a new name of your choice:</div><div><br /></div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYH_hkDepy8oavh08a8jrHEZMDZwdt9gbSo5ZDNeHGhyphenhyphenZBrG3ofkapj-Yw4tfyEeWjvPvKGRopJ9J47OQyJlrYkf7K8G5d5rMVigA8eCSnw08ZnwLdMf6kn1aWTclfOXqq3MS5yTVnSVS6/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="435" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYH_hkDepy8oavh08a8jrHEZMDZwdt9gbSo5ZDNeHGhyphenhyphenZBrG3ofkapj-Yw4tfyEeWjvPvKGRopJ9J47OQyJlrYkf7K8G5d5rMVigA8eCSnw08ZnwLdMf6kn1aWTclfOXqq3MS5yTVnSVS6/s16000/image.png" /></a></div><br /><br /><p></p><p></p><div class="separator" style="clear: both; text-align: center;"><br /></div>I can then select a tab colour, a useful aid when you have multiple worksheets:<p></p><p></p><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtXQkAP_iRHEPYFRBDgwp-HW7wzx4H0aRmU-S0CKJCzbFsk-IPvHEwYXI8QzdCvXdt9gURMJL0NPFKnmE95AueWLkagPVk7cCp9DEKt6cV9h5drD_Lky_bwc2qxzFMv7OWOsxAzwm0tKmt/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="443" data-original-width="582" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjtXQkAP_iRHEPYFRBDgwp-HW7wzx4H0aRmU-S0CKJCzbFsk-IPvHEwYXI8QzdCvXdt9gURMJL0NPFKnmE95AueWLkagPVk7cCp9DEKt6cV9h5drD_Lky_bwc2qxzFMv7OWOsxAzwm0tKmt/s16000/image.png" /></a></div><br /><br /></div><br /><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div>Click on the plus sign next to the worksheet tab and you can add a new worksheet, (by default called "Sheet2". I can rename and colour it too:<p></p><p></p><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkRBZJG2SgYgcq89rUo1OKEIKSbNyBEBCDfKlnLaQvod9Be-AIZkqfzigkDgBlE5wbuONr4x9BRMpdvdjxjYnzSHFMDyOEvXlJZTstAiOufAm8O9aGftBqNPlW9rYvWswLtQ9lA8lvISxr/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="409" data-original-width="631" height="374" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgkRBZJG2SgYgcq89rUo1OKEIKSbNyBEBCDfKlnLaQvod9Be-AIZkqfzigkDgBlE5wbuONr4x9BRMpdvdjxjYnzSHFMDyOEvXlJZTstAiOufAm8O9aGftBqNPlW9rYvWswLtQ9lA8lvISxr/w578-h374/image.png" width="578" /></a></div><br /><br /></div><br /><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div>Finally I can delete it by right clicking on the worksheet and selecting "Delete":</div><div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhy5iy85dfNCizrT975-EogU-5V18EUad4xoEa63cMftmRV8xSeyKrZePYwItk6Xl7nxfyisD3LPx0tAH1BiZIDHQsoriA3pcuMF3YfoNtZ1WFHi_coSebmfWfKRFSMA4K79XlK-nbo0Jge/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="377" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhy5iy85dfNCizrT975-EogU-5V18EUad4xoEa63cMftmRV8xSeyKrZePYwItk6Xl7nxfyisD3LPx0tAH1BiZIDHQsoriA3pcuMF3YfoNtZ1WFHi_coSebmfWfKRFSMA4K79XlK-nbo0Jge/s16000/image.png" /></a></div><br /><br /></div><div><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><br /><p></p><p><br /></p><p><br /></p><p><br /></p><p><br /></p><p>Have a play with the menu items that appear when you right click on a worksheet tab and see what else you can do! Comments below please. </p></div>Geek Dadhttp://www.blogger.com/profile/12159682162763730420noreply@blogger.com0tag:blogger.com,1999:blog-9130450719881994773.post-47516753192617097402021-03-20T18:10:00.023+00:002021-03-21T11:09:57.964+00:00Excel Hack - Quickly Get a Date in a Cell<p>Here's a quick Excel hack for you. If you frequently have to type dates in cells on a spreadsheet, simply hold down the ctrl (control) key and press the semi-colon key.</p><p>On the image below the Ctrl key is highlighted in white:</p><p></p><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirmRauG1ufUNSHOwTkjFfnr-rPMiCEJO7CcjgqbY4JA7fClszxFZJ_XCLWHrKysNs-68-7Dz8lx2_V_2xKm0LZfVyj-vOmriY_GRWJoWOatz_N84DQz8xsZFU7KoMMBBE3DfnmsLCBDxgx/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="170" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEirmRauG1ufUNSHOwTkjFfnr-rPMiCEJO7CcjgqbY4JA7fClszxFZJ_XCLWHrKysNs-68-7Dz8lx2_V_2xKm0LZfVyj-vOmriY_GRWJoWOatz_N84DQz8xsZFU7KoMMBBE3DfnmsLCBDxgx/s16000/image.png" /></a></div><br /><br /></div><br /><br /></div></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div>...and here's the semi-colon key:</div><div><p></p><p></p><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiq20SzCzUKx_AhxHd7o3QVrAhcc0narAULoevK0YmVpfjNAqABARdIc3wYmzIEcNitKPMbMBMnEQ5_xXxWlpP_YGGanxYJevm3O4IgCVFBikHQWSxclKgyDdFMQ_KhWY5U0e65YOFQAhK/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="168" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgiq20SzCzUKx_AhxHd7o3QVrAhcc0narAULoevK0YmVpfjNAqABARdIc3wYmzIEcNitKPMbMBMnEQ5_xXxWlpP_YGGanxYJevm3O4IgCVFBikHQWSxclKgyDdFMQ_KhWY5U0e65YOFQAhK/s16000/image.png" /></a></div><br /><br /></div><br /><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div><br /></div><div>(Note: This is a UK QWERTY keyboard).</div><div><p></p><p>So here I could get a load of dates in column A, simply by holding ctrl pressing and releasing the semi-colon key, release ctrl then pressing Enter! It took about 2 seconds!</p><p></p><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgglG3UGoaQn-yYPO7bQO3oOibZ9Y_6nIIVo0T9TxaUcY1MEfsbLfl0ychd46aavYiK2rqw9KB1fgrb9HeFCgad0hd6oIELlli2OxXK4T6GS4evdihYb0mDZ3T3fpXmRtDHNbok_70eBvL-/" style="clear: left; float: left; margin-bottom: 1em; margin-right: 1em;"><img alt="" data-original-height="601" data-original-width="567" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgglG3UGoaQn-yYPO7bQO3oOibZ9Y_6nIIVo0T9TxaUcY1MEfsbLfl0ychd46aavYiK2rqw9KB1fgrb9HeFCgad0hd6oIELlli2OxXK4T6GS4evdihYb0mDZ3T3fpXmRtDHNbok_70eBvL-/s16000/image.png" /></a></div><br /><br /></div><br /><br /><p></p><p></p><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><div class="separator" style="clear: both; text-align: center;"><br /></div><br /><br /></div><br /><br /></div><br /><br /><p></p><p><br /></p></div>Geek Dadhttp://www.blogger.com/profile/12159682162763730420noreply@blogger.com0tag:blogger.com,1999:blog-9130450719881994773.post-70911996686163787762021-03-20T17:37:00.001+00:002021-03-20T17:37:41.827+00:00Simple Excel Bank Account Tracker<p><span style="font-family: arial;">Here’s my guide to creating a super simple Excel spreadsheet
to help you keep track of your bank account.
For this I used Excel from Office 365 which, at the time of writing, was
on Version 2102</span></p><p class="MsoNormal"><span style="font-family: arial;"><o:p></o:p></span></p>
<p class="MsoNormal"><span style="font-family: arial;">First get a new Excel spreadsheet file and save it as
something meaningful.<span style="mso-spacerun: yes;"> </span>I called mine “bank_account_logger.xlsx”.<span style="mso-spacerun: yes;"> </span>Also double click on the worksheet tab and
call it something meaningful too (mine is “Bank spreadsheet”).<o:p></o:p></span></p><p class="MsoNormal"></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEii4S94v97TeFDIxSZ1YJF_wbyx90T-G6Z9i6ASvHxx0kLtK0SVoKHs6Qj72dFYi5RJu7BDncWQyG6wwiqZ3Y_avYhjhnpTNeBp0wmk95-L8EUv0E2CGBgA69brUZT8pwMHnkyCcCkmJ-T1/" style="margin-left: 1em; margin-right: 1em;"><span style="font-family: arial;"><img alt="" data-original-height="323" data-original-width="602" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEii4S94v97TeFDIxSZ1YJF_wbyx90T-G6Z9i6ASvHxx0kLtK0SVoKHs6Qj72dFYi5RJu7BDncWQyG6wwiqZ3Y_avYhjhnpTNeBp0wmk95-L8EUv0E2CGBgA69brUZT8pwMHnkyCcCkmJ-T1/s16000/image.png" /></span></a></div><span style="font-family: arial;"><br /></span><p class="MsoNormal"><span style="font-family: arial;">Next add some column headings and set the column widths.<span style="mso-spacerun: yes;"> </span>I added 4 headings:<o:p></o:p></span></p>
<p class="MsoNormal"><span style="font-family: arial;">Date, Description, Amount, Total<o:p></o:p></span></p>
<p class="MsoNormal"><span style="font-family: arial;">On the image below I show how to set the column width of the
date column.<span style="mso-spacerun: yes;"> </span>I selected the whole column
by clicking on the “A”, right clicked, selected “Column Width…” typed 10 in the
box that appears and clicked “OK”.</span></p><p class="MsoNormal"><span style="font-family: arial;"></span></p><div class="separator" style="clear: both; text-align: center;"><span style="font-family: arial;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhcY0eFjq37YilXivoWHI-0F2Fwn5mbyhyphenhyphenJJIRb7WG6pPjNQiIWcA1d96jo2HjD2RK4fCo7QkCkmXvqcXppLtmWsYqccsqz5lDeEph3dzjHBfjTqRIMObaSwHfYmtHKQ36OLAF_an2SJ48W/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="323" data-original-width="602" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhcY0eFjq37YilXivoWHI-0F2Fwn5mbyhyphenhyphenJJIRb7WG6pPjNQiIWcA1d96jo2HjD2RK4fCo7QkCkmXvqcXppLtmWsYqccsqz5lDeEph3dzjHBfjTqRIMObaSwHfYmtHKQ36OLAF_an2SJ48W/s16000/image.png" /></a></span></div><p class="MsoNormal"><span style="font-family: arial;">With the following column widths set the spreadsheet now
looks like the image below. Date = 10,
Description = 100, Amount = 15, Total = 15.</span></p><p></p><p class="MsoNormal"><span style="font-family: arial;"><o:p></o:p></span></p><p></p><p class="MsoNormal"><span style="font-family: arial;"></span></p><div class="separator" style="clear: both; text-align: center;"><span style="font-family: arial;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfhUSGaeWDtgzWhxLEmNwg34kR30S4HYJgTP1oRRw89-PiKOOEbWcYjhD5QLHjchUmAnlAInISvbsDxVgD7KLjDqJMSkoo8ovyORl8HOWbKbs7AD74jh8-3mx2rJbRKbgo9nnDQNwCiT_E/" style="margin-left: 1em; margin-right: 1em;"><img alt="" data-original-height="324" data-original-width="602" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgfhUSGaeWDtgzWhxLEmNwg34kR30S4HYJgTP1oRRw89-PiKOOEbWcYjhD5QLHjchUmAnlAInISvbsDxVgD7KLjDqJMSkoo8ovyORl8HOWbKbs7AD74jh8-3mx2rJbRKbgo9nnDQNwCiT_E/s16000/image.png" /></a></span></div><span style="font-family: arial;"><br /></span><p class="MsoNormal"><span style="font-family: arial;">You now need to do some formatting to make the spreadsheet
look good and work nicely.<span style="mso-spacerun: yes;"> </span>First freeze
the top row so that when you scroll down further than the first page you still
see the headings.<o:p></o:p></span></p>
<p class="MsoNormal"><span style="font-family: arial;">Do this by selecting the whole row by clicking on the “1”
and selecting the “View” menu, then “Freeze Panes” and “Freeze Top Row”.<o:p></o:p></span></p><p class="MsoNormal"></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXA8TBSTBx3_vMbnzhDsLy5GTEvbMvqpR7bRaoL-xTUznsxpuyaqLlqj4Rig3xG5_ghdlKQNasScxmWuyHz_FtqRKA3fOjG5tTc62X8_CJrV6fVmtZ3hwoIT8JpKzF6YsNipesaErSExvf/" style="margin-left: 1em; margin-right: 1em;"><span style="font-family: arial;"><img alt="" data-original-height="324" data-original-width="602" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjXA8TBSTBx3_vMbnzhDsLy5GTEvbMvqpR7bRaoL-xTUznsxpuyaqLlqj4Rig3xG5_ghdlKQNasScxmWuyHz_FtqRKA3fOjG5tTc62X8_CJrV6fVmtZ3hwoIT8JpKzF6YsNipesaErSExvf/s16000/image.png" /></span></a></div><span style="font-family: arial;"><br /></span><p></p><p class="MsoNormal"><span style="font-family: arial;">Just type today’s date in cell A2 and clever old Excel will
then recognise this as a date column forever more. I typed “20/3/21” in cell A2 and Excel replaced
it with my preferred, UK-centric, date format.
As a extra hack, you can hold down the control (ctrl) button and press
the ; key (semi-colon) and todays date will be entered in the cell.<o:p></o:p></span></p><span style="font-family: arial;"></span><p></p><p class="MsoNormal">
</p><p class="MsoNormal"><span style="font-family: arial;">The spreadsheet now looks like this:<o:p></o:p></span></p><p class="MsoNormal"></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgjJPkR8HVmcQ0g0nHvNu-OzhiUvu-4rNc7U1Vt6isQJjpzLUmbc-wJVvLQ6lYLaGigOylnebmugz3Y3wCtq81nq21YtFfmsSzulO6d14SoicGLNFBSa201pBdoh952Vc62izkpBiJFrEwq/" style="margin-left: 1em; margin-right: 1em;"><span style="font-family: arial;"><img alt="" data-original-height="324" data-original-width="602" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgjJPkR8HVmcQ0g0nHvNu-OzhiUvu-4rNc7U1Vt6isQJjpzLUmbc-wJVvLQ6lYLaGigOylnebmugz3Y3wCtq81nq21YtFfmsSzulO6d14SoicGLNFBSa201pBdoh952Vc62izkpBiJFrEwq/s16000/image.png" /></span></a></div><span style="font-family: arial;"><br /></span><p class="MsoNormal"><span style="font-family: arial;">The next column is description which will just contain text
so there’s no extra work to be done here.<o:p></o:p></span></p>
<p class="MsoNormal"><span style="font-family: arial;">The amount and Total columns will contain monetary values so
the Number format needs to be set to your local currency (British Pounds for
me).<span style="mso-spacerun: yes;"> </span><o:p></o:p></span></p>
<p class="MsoNormal"><span style="font-family: arial;">To do this, first select columns C and D.<span style="mso-spacerun: yes;"> </span>Do this by first clicking on the “C” at the
top of the 3<sup>rd</sup> column, then hold down the ctrl (control) key and
click on the “D”.<span style="mso-spacerun: yes;"> </span>Next in the “Home”
menu go to the “Number” area and click on the little slanting downward pointing arrow to
open the Format Cells menu.<o:p></o:p></span></p>
<p class="MsoNormal"><span style="font-family: arial;">Under category select “Accounting”, set decimal places to 2
and the symbol to the currency used in your country (so £ for me). <span style="mso-spacerun: yes;"> </span><o:p></o:p></span></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_R8kjwUwEUHV6Wi6zJqBpGcSHkji8Z6Jzi0yKbtD5Y6TcpmxVG5NsO_uf2NvEkbTv9-xCLSLx7n10QxuZ6Xs-_nYB_ZCY48SY6_PcFByo7uInAbtmp2kVFVGFkcZ62iDoxcIblXnOObCd/" style="margin-left: 1em; margin-right: 1em;"><span style="font-family: arial;"><img alt="" data-original-height="323" data-original-width="602" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi_R8kjwUwEUHV6Wi6zJqBpGcSHkji8Z6Jzi0yKbtD5Y6TcpmxVG5NsO_uf2NvEkbTv9-xCLSLx7n10QxuZ6Xs-_nYB_ZCY48SY6_PcFByo7uInAbtmp2kVFVGFkcZ62iDoxcIblXnOObCd/s16000/image.png" /></span></a></div><span style="font-family: arial;"><br /></span><p class="MsoNormal"><span style="font-family: arial;">Finally, as an optional advanced task, use Conditional
formatting to vary the colour of the Amount and Total columns depending on whether
they’re positive or negative.<o:p></o:p></span></p>
<p class="MsoNormal"><span style="font-family: arial;">In the “Home” menu select “Conditional Formatting”, “Highlight
Cells Rules” and “Greater Than…”<o:p></o:p></span></p><p class="MsoNormal"></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZ0ir-77_ppbnBQ2anWqrDENIevoYcJ01eveKzhxs__EnZJNUOsAc1h258x9wx6wCQluSYR6TxSEmQm2SyTc6-Le9mzh8i2VcOLTwKnAA4QgAV5sA8fd6doQeisP8tbJzEJQiO07xAZr34/" style="margin-left: 1em; margin-right: 1em;"><span style="font-family: arial;"><img alt="" data-original-height="338" data-original-width="602" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZ0ir-77_ppbnBQ2anWqrDENIevoYcJ01eveKzhxs__EnZJNUOsAc1h258x9wx6wCQluSYR6TxSEmQm2SyTc6-Le9mzh8i2VcOLTwKnAA4QgAV5sA8fd6doQeisP8tbJzEJQiO07xAZr34/s16000/image.png" /></span></a></div><span style="font-family: arial;"><br /></span><p></p><p class="MsoNormal"><span style="font-family: arial;">Type 0 in “Format cells that are GREATER THAN:” and select “Custom
Format…”.<o:p></o:p></span></p><p class="MsoNormal"></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHG7R0uWk1LQi5-FRWjdxtR-kk6XA0d6RSFMu5vg6LQ4NWsyeCHzn208cIMvks3yw-IwCOCzgDr-05fLIHTVoKu_cC-vTgrayFWYBPpju0mfYiYTAxBJ4lKUXAx2VO8PB6exhkv1s6OQwj/" style="margin-left: 1em; margin-right: 1em;"><span style="font-family: arial;"><img alt="" data-original-height="322" data-original-width="602" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjHG7R0uWk1LQi5-FRWjdxtR-kk6XA0d6RSFMu5vg6LQ4NWsyeCHzn208cIMvks3yw-IwCOCzgDr-05fLIHTVoKu_cC-vTgrayFWYBPpju0mfYiYTAxBJ4lKUXAx2VO8PB6exhkv1s6OQwj/s16000/image.png" /></span></a></div><p class="MsoNormal"><span style="font-family: arial;">Simply set the Color to Green and select “OK” and “OK”
again.<o:p></o:p></span></p><p class="MsoNormal"></p><div class="separator" style="clear: both; text-align: center;"><span style="font-family: arial;"><br /></span></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixbgz18A5q-2_kkXzSf_LAScFmOyVZQVFlhnTEOAQk0iiYHsMeMDJiRU09wPXmxIoDtJ3rYfD009gh5X88m0FTPWKyrnpm_AZWfL6dw8tf2uqJznzA3yRnMTNMPo-YRRY59dscmCEuVi9K/" style="margin-left: 1em; margin-right: 1em;"><span style="font-family: arial;"><img alt="" data-original-height="323" data-original-width="602" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEixbgz18A5q-2_kkXzSf_LAScFmOyVZQVFlhnTEOAQk0iiYHsMeMDJiRU09wPXmxIoDtJ3rYfD009gh5X88m0FTPWKyrnpm_AZWfL6dw8tf2uqJznzA3yRnMTNMPo-YRRY59dscmCEuVi9K/s16000/image.png" /></span></a></div><span style="font-family: arial;"><br /></span><p></p><p class="MsoNormal"><span style="font-family: arial;">Repeat the steps above but with a “Less Than…” rule, with “Format
cells that are LESS THAN:” set to 0 and a custom format to set the font colour
to Red.<o:p></o:p></span></p><p></p><p class="MsoNormal"><span style="font-family: arial;">If you like you can play with the cell formats, I just like
to select something that says “this is definitely positive” and “this is
definitely negative”.<o:p></o:p></span></p><p class="MsoNormal">
</p><p class="MsoNormal"><span style="font-family: arial;">Now you can create your first entry, a starting balance. In cell A2 make sure you have today’s date. In cell B2 write an informative description,
like that below. In cell C2 write your
current bank balance. In cell D2 type
the formula “=c2”. That just makes sure
your Total column also has your starting balance.<o:p></o:p></span></p><p class="MsoNormal"></p><div class="separator" style="clear: both; text-align: center;"><span style="font-family: arial;"><br /></span></div><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSHflbpLy9J-Gc1gYJ7GVzMgHahkrtvGWLtCKgyEVGpDz7s6hK31HGufBw4dXx3lDGBGsz527TYB1WAb5sO-YBWydW3itMAfE00y6oG2QAi5BC10aZFEBFaFxUOYiP7lyQl_mgUCYg-qdN/" style="margin-left: 1em; margin-right: 1em;"><span style="font-family: arial;"><img alt="" data-original-height="324" data-original-width="602" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhSHflbpLy9J-Gc1gYJ7GVzMgHahkrtvGWLtCKgyEVGpDz7s6hK31HGufBw4dXx3lDGBGsz527TYB1WAb5sO-YBWydW3itMAfE00y6oG2QAi5BC10aZFEBFaFxUOYiP7lyQl_mgUCYg-qdN/s16000/image.png" /></span></a></div><span style="font-family: arial;"><br /></span><p></p><p class="MsoNormal"><span style="font-family: arial;">You can now add your first transaction to the spreadsheet in
row 3. As before, enter the date, a good
description and the amount. Type the
Amount as a positive number for credits to your account and a negative number
for debits. In cell D3, type the formula
“=D2+C3”. What this means in practise is
“Take the Total from the row above and add the amount from this row”.</span></p><p class="MsoNormal"></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZGEVqKN4CEn-EqThEhNmIGWOhoWPMKtnFLMwHdtDI3NaxAJ98KrrftPPmVayVhxOLAryhiYiNxKRmQUksdelcVZDX76IIrOtL-2uygr1BFrwIHhDZ4JpuczWrQN7GZuvw7lYLYEV3NCv9/" style="margin-left: 1em; margin-right: 1em;"><span style="font-family: arial;"><img alt="" data-original-height="323" data-original-width="602" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZGEVqKN4CEn-EqThEhNmIGWOhoWPMKtnFLMwHdtDI3NaxAJ98KrrftPPmVayVhxOLAryhiYiNxKRmQUksdelcVZDX76IIrOtL-2uygr1BFrwIHhDZ4JpuczWrQN7GZuvw7lYLYEV3NCv9/s16000/image.png" /></span></a></div><p></p><p class="MsoNormal"><span style="font-family: arial;">You now get a running total in the Total column.<o:p></o:p></span></p><p class="MsoNormal"></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZLKp4-6MIynGEfGw7nMh3ZLJS6153_-URyx-J1yUxM9roV27J8HCJVOXJmvr11AjWJSs7JF9rh-RPxtlornjKgrnmvpkTihCKopgrB1eVL4lBAQNc5JaX0YP3Su0VVZZvsM1G8iKx3Cac/" style="margin-left: 1em; margin-right: 1em;"><span style="font-family: arial;"><img alt="" data-original-height="322" data-original-width="602" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhZLKp4-6MIynGEfGw7nMh3ZLJS6153_-URyx-J1yUxM9roV27J8HCJVOXJmvr11AjWJSs7JF9rh-RPxtlornjKgrnmvpkTihCKopgrB1eVL4lBAQNc5JaX0YP3Su0VVZZvsM1G8iKx3Cac/s16000/image.png" /></span></a></div><span style="font-family: arial;"><br /></span><p></p><p class="MsoNormal"><span style="font-family: arial;">When you want to put a debit on the sheet, just add as a
negative figure in the Amount column and use the same formula in the Total
column. I’ve typed this manually by way
of explanation but an easy way to do it is to select a cell with the formula
(cell D3 in this example), right click, select “Copy”, select the cell you want
the formula in (cell D4 in this example), right click select “Paste”.</span></p><p class="MsoNormal"></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhaqPikeUqyGeYx5tLftkucgoPvZpZ-Z2HqYp4Rp7dDrvofRrl1KWJobjFjIUGggXZx0Pq0RuB500CV5ptzn7MEBU1ybrkqXofiR4Ammmkc2u6gu8P0Y2aQcbDGa1v4e74PbIqsiEklBVHS/" style="margin-left: 1em; margin-right: 1em;"><span style="font-family: arial;"><img alt="" data-original-height="323" data-original-width="602" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhaqPikeUqyGeYx5tLftkucgoPvZpZ-Z2HqYp4Rp7dDrvofRrl1KWJobjFjIUGggXZx0Pq0RuB500CV5ptzn7MEBU1ybrkqXofiR4Ammmkc2u6gu8P0Y2aQcbDGa1v4e74PbIqsiEklBVHS/s16000/image.png" /></span></a></div><span style="font-family: arial;"><br /></span><p></p><p class="MsoNormal"><span style="font-family: arial;">So now the spreadsheet looks like this.<o:p></o:p></span></p><p class="MsoNormal"></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEguQkC4S5SjXuApRyb8LOcxBR9E4iNQzGn6usq61UVzCqtAtTZ6yObE_25rjpkkcs48j-oVBXHc4fdgS9l_L7dJ9VYqrFhJ3ANGfR8U44y0W2QXVAQJTgN7w5z6IcqMgFIaXcV5V143_cvn/" style="margin-left: 1em; margin-right: 1em;"><span style="font-family: arial;"><img alt="" data-original-height="324" data-original-width="602" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEguQkC4S5SjXuApRyb8LOcxBR9E4iNQzGn6usq61UVzCqtAtTZ6yObE_25rjpkkcs48j-oVBXHc4fdgS9l_L7dJ9VYqrFhJ3ANGfR8U44y0W2QXVAQJTgN7w5z6IcqMgFIaXcV5V143_cvn/s16000/image.png" /></span></a></div><span style="font-family: arial;"><br /></span><p></p><p class="MsoNormal"><span style="font-family: arial;">So now you every time you spend something or a bill goes out
you simply add a new row with the date, a description, the credit or debit
(written as a positive or negative number) and then update the Total by copying
the formula from the cell above. So you
end up with a spreadsheet that looks like this.<o:p></o:p></span></p><p class="MsoNormal"></p><div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVurcj7p02XQpYyEb61oMOAMBEgyfo2fG-SMbSpMlbIlrqHaY_hQYwlcru_iB9Du22HONEX_qI1YiD780YyrV2Ik87RM0DSn2wISdehVlKFSFAtOjyYRWJEvZZsNkuvBRF3fEWY0vLkHuk/" style="margin-left: 1em; margin-right: 1em;"><span style="font-family: arial;"><img alt="" data-original-height="323" data-original-width="602" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhVurcj7p02XQpYyEb61oMOAMBEgyfo2fG-SMbSpMlbIlrqHaY_hQYwlcru_iB9Du22HONEX_qI1YiD780YyrV2Ik87RM0DSn2wISdehVlKFSFAtOjyYRWJEvZZsNkuvBRF3fEWY0vLkHuk/s16000/image.png" /></span></a></div><span style="font-family: arial;"><br />Happy spreadsheeting!</span><p></p><p class="MsoNormal"><span style="font-family: arial;"><br /></span></p><p class="MsoNormal"><span style="font-family: arial;"><br /></span></p><p class="MsoNormal"><span style="font-family: arial;"><br /></span><br /></p><p></p>Geek Dadhttp://www.blogger.com/profile/12159682162763730420noreply@blogger.com0tag:blogger.com,1999:blog-9130450719881994773.post-50906937928001690052017-04-19T20:49:00.000+01:002017-04-19T20:49:27.125+01:00Half Marathon Comparison Using Azure, Google Maps, Python, MongoDB and JavascriptThis time last year I blogged about a <a href="http://pdwhomeautomation.blogspot.co.uk/2016/04/half-marathon-analysis-using-python-and.html" target="_blank">half-marathon I had run</a> where I paced it badly and slowed down massively at the end. I did the same race this year and ran a faster time but more importantly paced it more consistently and so enjoyed the experience more.<br />
<br />
The run was over the same course and the weather was similar so this provides a good opportunity to compare and contrast both years. At a superficial level, as part of the results that are provided you get to see your split after every 5K. Hence it was possible to compare the splits of last year with this year:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhoVsx5h8y8hD9m2kyA_xuQO5kEuDPx43X88i83KOfe80-r4KwwiPbVunuxK_sYNVdvnyALSfhX_bvoaFr8tvukKK62fqh7Tc0hb5jNUwK6LlKO0ZrO0f2UqG0ITHp38CfW3EzOrFMO9Xq6/s1600/image1.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="107" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhoVsx5h8y8hD9m2kyA_xuQO5kEuDPx43X88i83KOfe80-r4KwwiPbVunuxK_sYNVdvnyALSfhX_bvoaFr8tvukKK62fqh7Tc0hb5jNUwK6LlKO0ZrO0f2UqG0ITHp38CfW3EzOrFMO9Xq6/s400/image1.PNG" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
So simply put:</div>
<div class="separator" style="clear: both; text-align: left;">
</div>
<ul>
<li>After 5K, in 2017 I was 38 seconds behind where I was in 2016.</li>
<li>After 10K I was 44 seconds behind and after 15K I was a massive 80 seconds behind.</li>
<li>However after 20K in 2017 I had turned this around was 14 seconds ahead of 2016.</li>
<li>Then I ran the final 1.1K 27 seconds faster in 2017 than in 2016 to finish 41 seconds up.</li>
</ul>
Note that none of this was down to significantly better fitness, I just paced the run more sensibly in 2017. (Put differently I was a lot more stupid in 2016!).<br />
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
As a Geek I wanted to go further in this analysis so I thought it would be fun to visually compare 2016 versus 2017 on a map. i.e. See my 2016 self zoom past my 2017 self then see my 2017 self catch up and pass 2016. Having tinkered with AWS and Bluemix it was time to drive a different cloud computing offering so I decided to take up Microsoft's kind offer of £150 of credit. </div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Here's the result. The "6" marker is 2017, the "7" marker is 2017.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div style="text-align: center;">
<iframe width="320" height="266" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/Rwhb2JOdodE/0.jpg" src="https://www.youtube.com/embed/Rwhb2JOdodE?feature=player_embedded" frameborder="0" allowfullscreen></iframe></div>
<br />
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
So you can see:</div>
<div class="separator" style="clear: both; text-align: left;">
</div>
<ul>
<li>Me starting further up the road in 2017.</li>
<li>The 2016 me catching up and passing the 2017 me around the University.</li>
<li>2016 me staying ahead for a long period of time.</li>
<li>2017 me catching up and quickly passing 2016 me on the final straight stretch to the finish.</li>
</ul>
<br />
<div class="separator" style="clear: both; text-align: left;">
So a fascinating profile!</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Here's a diagram of what I put together. Full description and code then follows.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjy1XRQbrSDlTK7cjuc8TGf6cDkk8aPYNWgc-NoRecFO0UWr2SO-hae9_GoZ3NcqcgGJHivRDiOFTRrl-B1mZ3zMJEpT8B7gPPWBqcLoybLoCG3LaUCqMiOerF022Xf_4v21j0_bIMx9-jK/s1600/image2.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="361" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjy1XRQbrSDlTK7cjuc8TGf6cDkk8aPYNWgc-NoRecFO0UWr2SO-hae9_GoZ3NcqcgGJHivRDiOFTRrl-B1mZ3zMJEpT8B7gPPWBqcLoybLoCG3LaUCqMiOerF022Xf_4v21j0_bIMx9-jK/s640/image2.PNG" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
The above diagram shows the following key steps:</div>
<div class="separator" style="clear: both; text-align: left;">
</div>
<ul>
<li><a href="http://rover.ebay.com/rover/1/710-53481-19255-0/1?icep_ff3=9&pub=5575115886&toolid=10001&campid=5337649907&customid=&icep_uq=garmin+fenix&icep_sellerId=&icep_ex_kw=&icep_sortBy=12&icep_catId=&icep_minPrice=&icep_maxPrice=&ipn=psmain&icep_vectorid=229508&kwid=902099&mtid=824&kw=lg" target="_blank">Garmin Sports watch</a> syncs with Garmin Connect (standard activity)</li>
<li>GPX files downloaded from Garmin Connect and uploaded to Azure Virtual Machine (covered in Step 1 below).</li>
<li>Python script to parse GPX files and load them in a MongoDB instance (Step 2)</li>
<li>Apache webserver and Python cgi-bin to extract data from the MongoDB instance and offer a simple API (step 3)</li>
<li>HTML, CSS and Javascript to access API and present animated map markers using the Google Maps Javascript API (step 4)</li>
</ul>
<b>Step 1 - Getting a Azure Linux Virtual Machine</b><br />
Microsoft Azure is very easy and intuitive to use. I already had a Microsoft account for Outlook.com so just used this to go through the Azure <a href="https://azure.microsoft.com/en-gb/free/" target="_blank">free trial sign up process</a>. This gave me £150 worth of free credit on the Azure platform. <br />
<br />
After quickly reviewing tutorials I requested a Linux Virtual Machine using the steps New - Compute - Ubuntu Server 16.04TS and then providing some basic configuration details. Within roughly a minute the server had been setup and I could get details as to how to SSH onto the VM using <a href="http://www.putty.org/" target="_blank">PuTTY</a>. The size of the platform was Standard DS1 v2 (1 core, 3.5 GB memory) which was suitable for my needs.<br />
<br />
A tile on the Azure dashboard gave me access to all manner of information and configuration options for the VM. Example below:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_ku6B_VdPz8KRPqkowY7HI9aTxDADQJfis-q5JIzVNbp1Wta5rCT2NzATAl3yKV_dBuVhHfuf0jn1ywx2JV96LUPIhordtOcU7Usy1XEwOOJddID2exaC_m37Wu3FyQ-6t5o9fEsmGoUt/s1600/image3.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="206" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh_ku6B_VdPz8KRPqkowY7HI9aTxDADQJfis-q5JIzVNbp1Wta5rCT2NzATAl3yKV_dBuVhHfuf0jn1ywx2JV96LUPIhordtOcU7Usy1XEwOOJddID2exaC_m37Wu3FyQ-6t5o9fEsmGoUt/s640/image3.PNG" width="640" /></a></div>
<br />
<br />
Take a step back now - for an olde skool Technology guy such as myself I am still super impressed by cloud computing capabilities. No massive forms to fill out, no tetchy administrators to haggle with, no IP networking to organise - just BOOM! and you've got a machine to play with.<br />
<br />
The final part of this step was to use an FTP client (WinSCP) to upload the Garmin GPX files to the VM.<br />
<br />
<b>Step 2 - MongoDB, GPX File Parsing and Database Loading</b><br />
The plan was to use the GPX files recorded by my <a href="http://rover.ebay.com/rover/1/710-53481-19255-0/1?icep_ff3=9&pub=5575115886&toolid=10001&campid=5337649907&customid=&icep_uq=garmin+fenix&icep_sellerId=&icep_ex_kw=&icep_sortBy=12&icep_catId=&icep_minPrice=&icep_maxPrice=&ipn=psmain&icep_vectorid=229508&kwid=902099&mtid=824&kw=lg" target="_blank">Garmin</a> sports watch in 2016 and 2017 to allow map markers to be animated. So what's a GPX file? Here's a definition:<br />
<br />
<b style="background-color: white; color: #222222; font-family: arial, sans-serif; font-size: 16px;">GPX</b><span style="background-color: white; color: #222222; font-family: "arial" , sans-serif; font-size: 16px;">, or GPS Exchange Format, is an XML schema designed as a common GPS data format for software applications. It can be used to describe waypoints, tracks, and routes. The format is open and can be used without the need to pay license fees.</span><br />
<br />
Here's the top section of one of my half-marathon GPX files:<br />
<br />
<code>
<?xml version="1.0" encoding="UTF-8"?><br />
<gpx creator="Garmin Connect" version="1.1"<br />
xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/11.xsd"<br />
xmlns="http://www.topografix.com/GPX/1/1"<br />
xmlns:ns3="http://www.garmin.com/xmlschemas/TrackPointExtension/v1"<br />
xmlns:ns2="http://www.garmin.com/xmlschemas/GpxExtensions/v3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><br />
<metadata><br />
<link href="connect.garmin.com"><br />
<text>Garmin Connect</text><br />
</link><br />
<time>2016-04-03T09:19:19.000Z</time><br />
</metadata><br />
<trk><br />
<name>Whitley Ward Running</name><br />
<type>running</type><br />
<trkseg><br />
<trkpt lat="51.42623794265091419219970703125" lon="-0.992680527269840240478515625"><br />
<ele>45.40000152587890625</ele><br />
<time>2016-04-03T09:19:19.000Z</time><br />
<extensions><br />
<ns3:TrackPointExtension><br />
<ns3:hr>82</ns3:hr><br />
</ns3:TrackPointExtension><br />
</extensions><br />
</trkpt><br />
<trkpt lat="51.42622503452003002166748046875" lon="-0.9927202574908733367919921875"><br />
<ele>45.40000152587890625</ele><br />
<time>2016-04-03T09:19:20.000Z</time><br />
<extensions><br />
<ns3:TrackPointExtension><br />
<ns3:hr>82</ns3:hr><br />
</ns3:TrackPointExtension><br />
</extensions><br />
</trkpt><br />
<br />
</code>So some metadata and then a <trk> section with a <trkseg> subsection which is a container for a bunch of <trkpt> elements. Within each of these you can see:<br />
<br />
<ul>
<li>The position logged (latitude and longitude)</li>
<li>Elevation</li>
<li>Date and time</li>
<li>Heart rate </li>
</ul>
<br />
I wanted to store all the data in a database and I chose to use MongoDB on Azure as I enjoyed using it for a <a href="http://pdwhomeautomation.blogspot.co.uk/2016/03/first-football-soccer-stats-analysis.html" target="_blank">Raspberry Pi project</a> last year (and so also had cracked using Python to write to and read from the database).<br />
<br />
Getting the database was super easy. Within the Azure I did New - Databases - "Database as a Service for MongoDB", entered a few details and minute or two later had a MongoDB instance.<br />
<br />
Remembering that the structure of a document database is different to a relational database as follows:<br />
<br />
<table border="1" style="background-color: white; color: #222222; font-family: Arial, Tahoma, Helvetica, FreeSans, sans-serif; font-size: 13.2px;"><tbody>
<tr><th>Relational Database Term</th><th>Document Database Term</th></tr>
<tr><td>Database</td><td>Database</td></tr>
<tr><td>Table</td><td>Collection</td></tr>
<tr><td>Row</td><td>Document</td></tr>
</tbody></table>
<br />
...I set up a database called "geekmongo" and created a collection within it called "Test". Hence the task at hand was to parse the GPX files, create JSON documents and write them to the Test collection in the geekmongo database.<br />
<br />
<code>
#Import statements<br />
import xml.etree.ElementTree as ET<br />
from datetime import datetime<br />
from pymongo import MongoClient<br />
<br />
#File to process<br />
FileOne = '/home/map/rhm/activity_1109977624_2016.gpx'<br />
<br />
#Database related - Got all these from "Connection String" area on Azure for the database instance<br />
#Created the collection test myself manually on Azure<br />
dBAddress = 'Your Connection String'<br />
<br />
#Start parsing that XML<br />
tree = ET.parse(FileOne)<br />
root = tree.getroot()<br />
<br />
#Set up for the mongo instance, the database then the collection<br />
#Connect to the database<br />
client = MongoClient(dBAddress)<br />
db = client.geekmongo<br />
collection = db.Test<br />
<br />
#Get the first timestamp as we'll reference all subsequent ones to this in order to be able to calculate elapsed timestamp<br />
FirstTimeStamp = root[1][2][0][1].text<br />
#Turn it into a time object we can use<br />
TStart = datetime.strptime(FirstTimeStamp[:-5],"%Y-%m-%dT%H:%M:%S")<br />
<br />
#Loop through the XML file picking out lat and lng and writing them to a file, (unless they're the last ones on the list)<br />
#Example is {"elapsed": 0, "lat":51.4566827,"lng":-0.9690389},<br />
LoopVar = 0<br />
for myTrkpt in root[1][2]:<br />
#Calculate the elapsed time<br />
TimeNow = myTrkpt[1].text<br />
TNow = datetime.strptime(TimeNow[:-5],"%Y-%m-%dT%H:%M:%S")<br />
TimeElapsed = abs(TNow - TStart).seconds<br />
<br />
#Build a Python dictionary that we'll write to the MongoDB<br />
MongoDoc = {}<br />
MongoDoc["elapsed"] = TimeElapsed<br />
MongoDoc["lat"] = myTrkpt.attrib.get('lat')<br />
MongoDoc["lng"] = myTrkpt.attrib.get('lon')<br />
MongoDoc["elevation"] = myTrkpt[0].text<br />
MongoDoc["timestamp"] = TimeNow[:-5]<br />
MongoDoc["heart"] = myTrkpt[2][0][0].text<br />
MongoDoc["cadence"] = 0<br />
<br />
#Write the document to the footie collection<br />
collection.insert_one(MongoDoc)<br />
<br />
print("Done") <br />
</code><br />
<div>
<code><span style="font-family: inherit;"><br /></span></code></div>
So here I use the Python XML and pymongo modules to parse the GPX files and write to the database respectively.<br />
<div>
<br /></div>
<div>
With pymongo you create an object and then connect to the database using a "Connection String". You get this from the <span style="font-family: inherit;">Azure management console for the MongoDB instance under the "Connection String" settings area</span>. This string contains the database address and the credentials required to access it. You then can create a collection object which you write documents to using the insert_one() method.</div>
<div>
<br /></div>
<div>
Using the XML module you create an object called "root" and can use indices to access the different parts of the GPX structure. So for example root[0] will be the first part of the GPX file.</div>
<div>
<br /></div>
<div>
The code then loops through the <trkpt> elements of the GPX file, picks out all the relevant data and then creates a Python dictionary which will be written to the database. I also calculate an "elapsed" field which is the difference in seconds between the first <trkpt> elements and the element in question. I foresee this being useful later...</div>
<div>
<br /></div>
<div>
It was interesting to look at the Azure console as I ran the scripts to parse the GPX files and write documents to the database. Here's what was shown:</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDYeVN4GKDDIn156yTbp_sqLScmvFvcjasfkZ168KBxwYQ2WTqxGADmXk2fbAGXUoHS1UpjRSjey2tTISUpjcgsKyTFx5eDrMB6Y-pgSdZH4Sjl15w5zL5druSk_v57BzyKX0A9gXxUvJt/s1600/image4.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="227" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDYeVN4GKDDIn156yTbp_sqLScmvFvcjasfkZ168KBxwYQ2WTqxGADmXk2fbAGXUoHS1UpjRSjey2tTISUpjcgsKyTFx5eDrMB6Y-pgSdZH4Sjl15w5zL5druSk_v57BzyKX0A9gXxUvJt/s400/image4.PNG" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
Here you can see a peaks "insert" requests as the data was being inserted. The two peaks represent the two separate files being parsed and loaded.</div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
<b>Step 3 - A Web Server and an API</b></div>
<div class="separator" style="clear: both; text-align: left;">
I wanted to create an API such that Javascript running within a browser could make a AJAX request to extract the data. At some point I'll explore an Azure Web App for this sort of thing but for now I decided to use an Apache web server running on the Azure Linux VM and use a cgi-bin Python script to provide the functionality of the API. I simply ran the <span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">sudo apt-get install apache2</span> command to install Apache and used <a href="https://www.server-world.info/en/note?os=Ubuntu_16.04&p=httpd&f=5" target="_blank">this guide</a> to get cgi-bin working for Python scripts.</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
To get the web server to work I had to do some configuration within the Azure console. Specifically I had to configure a rule to enable HTTP traffic (port 80) on the platform. To do this I selected the VM from the console, selected "Network Interfaces" then selected the Network Security Group. I then configured the "HTTPRule" shown below:</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQ6Up7wYtn3JImPCvrPXMhIfivdA9-wjFtd4xBmjtFPCuIyzEWN5gs1c2UQsbJxNRr3NGdrEK-rMfSnmRjF-8r_hSmvTw1DsVQMc2CXlOYpN5lZtucXvfOlwCQU6mzCA6ulNH_oJ5oNkJp/s1600/image5.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="105" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgQ6Up7wYtn3JImPCvrPXMhIfivdA9-wjFtd4xBmjtFPCuIyzEWN5gs1c2UQsbJxNRr3NGdrEK-rMfSnmRjF-8r_hSmvTw1DsVQMc2CXlOYpN5lZtucXvfOlwCQU6mzCA6ulNH_oJ5oNkJp/s640/image5.PNG" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div>
To create the API I wrote the following Python script:</div>
<code>
</code>
<br />
<div>
<code><br /></code></div>
<code>
</code>
<br />
<div>
<div>
<code>#!/usr/bin/env python</code></div>
<div>
<code><br /></code></div>
<div>
<code>#Import statements</code></div>
<div>
<code>from pymongo import MongoClient</code></div>
<div>
<code>import cgi</code></div>
<div>
<code>import re</code></div>
<div>
<code>import cgitb</code></div>
<div>
<code><br /></code></div>
<div>
<code>#Enable error logging</code></div>
<div>
<code>cgitb.enable()</code></div>
<div>
<code><br /></code></div>
<div>
<code>#Database related - Got all these from "Connection String" area on Azure for the database instance</code></div>
<div>
<code>dBAddress = 'Your Connection String' </code></div>
<div>
<code>CollectionID = 'Test'</code></div>
<div>
<code><br /></code></div>
<div>
<code>#Get the query string parameters provided. 'name' field is the mongo name, 'value' field is the value</code></div>
<div>
<code>arguments = cgi.FieldStorage()</code></div>
<div>
<code>MongoName = arguments['name'].value</code></div>
<div>
<code>MongoValue = arguments['value'].value</code></div>
<div>
<code><br /></code></div>
<div>
<code>#Form the document to use for the database access. We will do a Regex because we may be searching on a partial date</code></div>
<div>
<code>MongoRegex = {}</code></div>
<div>
<code>MongoRegex['$regex'] = MongoValue</code></div>
<div>
<code>MongoDoc = {}</code></div>
<div>
<code>MongoDoc[MongoName] = MongoRegex</code></div>
<div>
<code><br /></code></div>
<div>
<code>#Connect to the database, get a database object and get a collection</code></div>
<div>
<code>client = MongoClient(dBAddress)</code></div>
<div>
<code>db = client.geekmongo</code></div>
<div>
<code>collection = db.Test</code></div>
<div>
<code><br /></code></div>
<div>
<code>print ('content-type: application/json\n\n')</code></div>
<div>
<code><br /></code></div>
<div>
<code>#Get the total number of documents returned and set up a counter variable</code></div>
<div>
<code>TotalDocCount = collection.count(MongoDoc)</code></div>
<div>
<code>DocCounter = 0</code></div>
<div>
<code><br /></code></div>
<div>
<code>#Start the output string</code></div>
<div>
<code>OutString = '{"markers":['</code></div>
<div>
<code><br /></code></div>
<div>
<code>#Do a database find based upon the parameters provided. Use this to form the output. Need elapsed (integer), lat (long 4dp), lng (long 4dp)</code></div>
<div>
<code>#elevation (1dp), timestamp (string) and heart rate (integer)</code></div>
<div>
<code>for rhmDoc in collection.find(MongoDoc):</code></div>
<div>
<code> OutString = OutString + '{"elapsed":' + str(rhmDoc["elapsed"]) + ','</code></div>
<div>
<code> OutString = OutString + '"lat":' + str(round(float(rhmDoc["lat"]),4)) + ',' </code></div>
<div>
<code> OutString = OutString + '"lng":' + str(round(float(rhmDoc["lng"]),4)) + ','</code></div>
<div>
<code> OutString = OutString + '"elevation":' + str(round(float(rhmDoc["elevation"]),1)) + ','</code></div>
<div>
<code> OutString = OutString + '"timestamp":' + chr(34) + rhmDoc["timestamp"] + '",'</code></div>
<div>
<code> OutString = OutString + '"heart":' + str(rhmDoc["heart"]) + '}'</code></div>
<div>
<code><br /></code></div>
<div>
<code> #See how many documents we've dealt with and whether we need to add a , to the end of the document</code></div>
<div>
<code> DocCounter += 1</code></div>
<div>
<code> #If we're on the last document then we add the ] to close the JSON array</code></div>
<div>
<code> if (DocCounter == TotalDocCount):</code></div>
<div>
<code> OutString = OutString + '],'</code></div>
<div>
<code> else:</code></div>
<div>
<code> OutString = OutString + ','</code></div>
<div>
<code><br /></code></div>
<div>
<code>#Add the total items part</code></div>
<div>
<code>OutString = OutString + '"TotalItems":' + str(TotalDocCount) + '}'</code></div>
<div>
<code><br /></code></div>
<div>
<code>#Stream the output to the client</code></div>
<div>
<code>print OutString</code></div>
<div>
<code><span style="font-family: Arial, Helvetica, sans-serif;"><br /></span></code></div>
<div>
<code><span style="font-family: Arial, Helvetica, sans-serif;">Here I use the CGI module to read query string parameters provided by the client. So for example the URL:</span></code><br />
<code><span style="font-family: Arial, Helvetica, sans-serif;"><span style="font-family: "times new roman";"><br /></span>
<span style="font-family: "times new roman";">http://<server URL>/GetGPXData.py?name=heart&value=82</span></span></code><br />
<code><span style="font-family: Arial, Helvetica, sans-serif;"><span style="font-family: "times new roman";"><br /></span>
<span style="font-family: "times new roman";">...will result in a database query being made for all documents that contain the heart rate value of 82.</span></span></code></div>
<div>
<code><code><span style="font-family: Arial, Helvetica, sans-serif;"><span style="font-family: "times new roman";"><br /></span><span style="font-family: "times new roman";">The script then takes the response of the database query and forms a string JSON document with all the values to pass back to the client.</span>
</span></code></code><br />
<code><span style="font-family: Courier New, Courier, monospace;"><code><span style="font-family: "times new roman";"><br /></span></code></span></code>
<span style="font-family: Arial, Helvetica, sans-serif;">The "regex" component of the MongoDB query document means you can do a "contains" search on the database. i.e. "contains 2016" to return all the values for 2016.</span><br />
<span style="font-family: Arial, Helvetica, sans-serif;"><br /></span>
<span style="font-family: Arial, Helvetica, sans-serif;"><b>Step 4 - Web Page, Javascript and Google Maps API</b></span><br />
<span style="font-family: Arial, Helvetica, sans-serif;">So the final part of the project was to write a web page that could use Javascript to a)download data from the API I just created and b)plot it on a map using the Google Maps API.</span><br />
<span style="font-family: Arial, Helvetica, sans-serif;">
</span>
<ul>
<li><code><code><span style="font-family: Arial, Helvetica, sans-serif;">The HTML, CSS and Javascript is below. Highlights:</span></code></code></li>
<li><code><code><span style="font-family: Arial, Helvetica, sans-serif;">Uses Google maps Javascript API to bring up a map, place and move markers. I started with <a href="https://developers.google.com/maps/documentation/javascript/adding-a-google-map" target="_blank">this tutorial</a>.</span></code></code></li>
<li><code><code><span style="font-family: Arial, Helvetica, sans-serif;">Uses the API previously described to acquire the position data to plot (function startRace).</span></code></code></li>
<li><code><code><span style="font-family: Arial, Helvetica, sans-serif;">Uses a Javascript interval to "fire" and cause an assessment of position and the map to be updated</span></code></code></li>
<li><code><code><span style="font-family: Arial, Helvetica, sans-serif;">Calculates the straight line distance between the markers.</span></code></code></li>
</ul>
<table><tbody>
<tr><td class="line-number" value="1"></td><td class="line-content"><span class="html-doctype"><!DOCTYPE html></span>
</td></tr>
<tr><td class="line-number" value="2"></td><td class="line-content"><span class="html-tag"><html></span>
</td></tr>
<tr><td class="line-number" value="3"></td><td class="line-content"><span class="html-tag"><head></span>
</td></tr>
<tr><td class="line-number" value="4"></td><td class="line-content"><span class="html-tag"><style></span>
</td></tr>
<tr><td class="line-number" value="5"></td><td class="line-content">#map {
</td></tr>
<tr><td class="line-number" value="6"></td><td class="line-content">height: 500px;
</td></tr>
<tr><td class="line-number" value="7"></td><td class="line-content">width: 100%;
</td></tr>
<tr><td class="line-number" value="8"></td><td class="line-content">}
</td></tr>
<tr><td class="line-number" value="9"></td><td class="line-content"><span class="html-tag"></style></span>
</td></tr>
<tr><td class="line-number" value="10"></td><td class="line-content"><br /></td></tr>
<tr><td class="line-number" value="12"></td><td class="line-content"><span class="html-tag"></head></span>
</td></tr>
<tr><td class="line-number" value="13"></td><td class="line-content"><span class="html-tag"><body></span>
</td></tr>
<tr><td class="line-number" value="14"></td><td class="line-content"><span class="html-tag"><h3></span>Reading Half Marathon Analysis<span class="html-tag"></h3></span>
</td></tr>
<tr><td class="line-number" value="15"></td><td class="line-content"><span class="html-tag"><div <span class="html-attribute-name">id</span>="<span class="html-attribute-value">map</span>"></span><span class="html-tag"></div></span>
</td></tr>
<tr><td class="line-number" value="16"></td><td class="line-content"><span class="html-tag"><input <span class="html-attribute-name">type</span>='<span class="html-attribute-value">button</span>' <span class="html-attribute-name">id</span>='<span class="html-attribute-value">btnLoad</span>' <span class="html-attribute-name">value</span>='<span class="html-attribute-value">Load One</span>' <span class="html-attribute-name">onclick</span>='<span class="html-attribute-value">loadLocsOne();</span>'></span>
</td></tr>
<tr><td class="line-number" value="17"></td><td class="line-content"><span class="html-tag"><input <span class="html-attribute-name">type</span>='<span class="html-attribute-value">button</span>' <span class="html-attribute-name">id</span>='<span class="html-attribute-value">btnLoad</span>' <span class="html-attribute-name">value</span>='<span class="html-attribute-value">Load Two</span>' <span class="html-attribute-name">onclick</span>='<span class="html-attribute-value">loadLocsTwo();</span>'></span>
</td></tr>
<tr><td class="line-number" value="18"></td><td class="line-content"><span class="html-tag"><p <span class="html-attribute-name">id</span>="<span class="html-attribute-value">Distance</span>"></span><span class="html-tag"></p></span>
</td></tr>
<tr><td class="line-number" value="19"></td><td class="line-content"><span class="html-tag"><input <span class="html-attribute-name">type</span>='<span class="html-attribute-value">button</span>' <span class="html-attribute-name">id</span>='<span class="html-attribute-value">btnLoad</span>' <span class="html-attribute-name">value</span>='<span class="html-attribute-value">Race</span>' <span class="html-attribute-name">onclick</span>='<span class="html-attribute-value">startRace();</span>'></span>
</td></tr>
<tr><td class="line-number" value="20"></td><td class="line-content"><span class="html-tag"><input <span class="html-attribute-name">type</span>='<span class="html-attribute-value">button</span>' <span class="html-attribute-name">id</span>='<span class="html-attribute-value">btnLoad</span>' <span class="html-attribute-name">value</span>='<span class="html-attribute-value">Stop</span>' <span class="html-attribute-name">onclick</span>='<span class="html-attribute-value">stopRace();</span>'></span>
</td></tr>
<tr><td class="line-number" value="21"></td><td class="line-content"><span class="html-tag"><input <span class="html-attribute-name">type</span>='<span class="html-attribute-value">button</span>' <span class="html-attribute-name">id</span>='<span class="html-attribute-value">btnLoad</span>' <span class="html-attribute-name">value</span>='<span class="html-attribute-value">Heart Chart</span>' <span class="html-attribute-name">onclick</span>='<span class="html-attribute-value">heartChart();</span>'></span>
</td></tr>
<tr><td class="line-number" value="22"></td><td class="line-content"></td></tr>
<tr><td class="line-number" value="23"></td><td class="line-content"><span class="html-tag"><script <span class="html-attribute-name">type</span>="<span class="html-attribute-value">text/javascript</span>"></span>
</td></tr>
<tr><td class="line-number" value="24"></td><td class="line-content">//This is V10 that adds getting the data from an 'API'
</td></tr>
<tr><td class="line-number" value="25"></td><td class="line-content">//Some nasty global variables. Discovered needed to use setInterval to control a marker and these were needed for that.
</td></tr>
<tr><td class="line-number" value="26"></td><td class="line-content">var map //Enables us to reference the map in all parts of the code
</td></tr>
<tr><td class="line-number" value="27"></td><td class="line-content">var markerOne //A marker entity
</td></tr>
<tr><td class="line-number" value="28"></td><td class="line-content">var markerTwo //A marker entity
</td></tr>
<tr><td class="line-number" value="29"></td><td class="line-content">var timeElapsed //A variable to hold how many seconds have elapsed
</td></tr>
<tr><td class="line-number" value="30"></td><td class="line-content">var maxElapsed //Defines the maximum elapsed time we'll have across the two JSON structures
</td></tr>
<tr><td class="line-number" value="31"></td><td class="line-content">var locsOne = {}; //A position array
</td></tr>
<tr><td class="line-number" value="32"></td><td class="line-content">var locsTwo = {}; //A position array
</td></tr>
<tr><td class="line-number" value="33"></td><td class="line-content">var intervalVar //Use for the setInterval thingy
</td></tr>
<tr><td class="line-number" value="34"></td><td class="line-content">var raceStarted //Boolean that defines whether the race has started
</td></tr>
<tr><td class="line-number" value="35"></td><td class="line-content"></td></tr>
<tr><td class="line-number" value="36"></td><td class="line-content">//Initialise the map and put a marker on it
</td></tr>
<tr><td class="line-number" value="37"></td><td class="line-content">function initMap() {
</td></tr>
<tr><td class="line-number" value="38"></td><td class="line-content">var readingOne = {lat: 51.4366827, lng: -0.9680389};
</td></tr>
<tr><td class="line-number" value="39"></td><td class="line-content">var readingTwo = {lat: 51.4466827, lng: -0.9780389};
</td></tr>
<tr><td class="line-number" value="40"></td><td class="line-content">map = new google.maps.Map(document.getElementById('map'), {
</td></tr>
<tr><td class="line-number" value="41"></td><td class="line-content">zoom: 13,
</td></tr>
<tr><td class="line-number" value="42"></td><td class="line-content">center: readingOne
</td></tr>
<tr><td class="line-number" value="43"></td><td class="line-content">});
</td></tr>
<tr><td class="line-number" value="44"></td><td class="line-content">markerOne = new google.maps.Marker({
</td></tr>
<tr><td class="line-number" value="45"></td><td class="line-content">position: readingOne,
</td></tr>
<tr><td class="line-number" value="46"></td><td class="line-content">map: map,
</td></tr>
<tr><td class="line-number" value="47"></td><td class="line-content">label: "6"
</td></tr>
<tr><td class="line-number" value="48"></td><td class="line-content">});
</td></tr>
<tr><td class="line-number" value="49"></td><td class="line-content">markerTwo = new google.maps.Marker({
</td></tr>
<tr><td class="line-number" value="50"></td><td class="line-content">position: readingTwo,
</td></tr>
<tr><td class="line-number" value="51"></td><td class="line-content">map: map,
</td></tr>
<tr><td class="line-number" value="52"></td><td class="line-content">label: "7"
</td></tr>
<tr><td class="line-number" value="53"></td><td class="line-content">//color: 0xFFFFFF
</td></tr>
<tr><td class="line-number" value="54"></td><td class="line-content">});
</td></tr>
<tr><td class="line-number" value="55"></td><td class="line-content"></td></tr>
<tr><td class="line-number" value="56"></td><td class="line-content">//This is a load or reload so state that the race is not started
</td></tr>
<tr><td class="line-number" value="57"></td><td class="line-content">raceStarted = false;
</td></tr>
<tr><td class="line-number" value="58"></td><td class="line-content">}
</td></tr>
<tr><td class="line-number" value="59"></td><td class="line-content"></td></tr>
<tr><td class="line-number" value="60"></td><td class="line-content">//Just move a marker
</td></tr>
<tr><td class="line-number" value="61"></td><td class="line-content">function positionMarker(inMarker, inLat, inLng)
</td></tr>
<tr><td class="line-number" value="62"></td><td class="line-content">{
</td></tr>
<tr><td class="line-number" value="63"></td><td class="line-content">//Set the position of the marker
</td></tr>
<tr><td class="line-number" value="64"></td><td class="line-content">var newPos = {lat: inLat, lng: inLng};
</td></tr>
<tr><td class="line-number" value="65"></td><td class="line-content"></td></tr>
<tr><td class="line-number" value="66"></td><td class="line-content">//Set the position of the marker
</td></tr>
<tr><td class="line-number" value="67"></td><td class="line-content">inMarker.setPosition(newPos);
</td></tr>
<tr><td class="line-number" value="68"></td><td class="line-content">}
</td></tr>
<tr><td class="line-number" value="69"></td><td class="line-content"></td></tr>
<tr><td class="line-number" value="70"></td><td class="line-content">//Initialises matters when user presses "Race"
</td></tr>
<tr><td class="line-number" value="71"></td><td class="line-content">function startRace()
</td></tr>
<tr><td class="line-number" value="72"></td><td class="line-content">{
</td></tr>
<tr><td class="line-number" value="73"></td><td class="line-content">//What we do first depends on whether the race is started!
</td></tr>
<tr><td class="line-number" value="74"></td><td class="line-content">if (raceStarted == false)
</td></tr>
<tr><td class="line-number" value="75"></td><td class="line-content">{
</td></tr>
<tr><td class="line-number" value="76"></td><td class="line-content">//Initialise the position number
</td></tr>
<tr><td class="line-number" value="77"></td><td class="line-content">timeElapsed = 0;
</td></tr>
<tr><td class="line-number" value="78"></td><td class="line-content"></td></tr>
<tr><td class="line-number" value="79"></td><td class="line-content">//We need to find out the max elapsed time across the two structures. In this way we'll increment the elapsed time every time the interval handler
</td></tr>
<tr><td class="line-number" value="80"></td><td class="line-content">//fires. If we find a position we update the marker. If not we leave the marker where it is. When we've exhausted all possible elapsed times then
</td></tr>
<tr><td class="line-number" value="81"></td><td class="line-content">//we know to stop the handler
</td></tr>
<tr><td class="line-number" value="82"></td><td class="line-content">var maxElapsedOne = locsOne.markers[locsOne.TotalItems - 1].elapsed;
</td></tr>
<tr><td class="line-number" value="83"></td><td class="line-content">var maxElapsedTwo = locsTwo.markers[locsTwo.TotalItems - 1].elapsed;
</td></tr>
<tr><td class="line-number" value="84"></td><td class="line-content"></td></tr>
<tr><td class="line-number" value="85"></td><td class="line-content">//Set up the max elapsed value
</td></tr>
<tr><td class="line-number" value="86"></td><td class="line-content">if (maxElapsedOne > maxElapsedTwo)
</td></tr>
<tr><td class="line-number" value="87"></td><td class="line-content">{
</td></tr>
<tr><td class="line-number" value="88"></td><td class="line-content">maxElapsed = maxElapsedOne;
</td></tr>
<tr><td class="line-number" value="89"></td><td class="line-content">}
</td></tr>
<tr><td class="line-number" value="90"></td><td class="line-content">else
</td></tr>
<tr><td class="line-number" value="91"></td><td class="line-content">{
</td></tr>
<tr><td class="line-number" value="92"></td><td class="line-content">maxElapsed = maxElapsedTwo;
</td></tr>
<tr><td class="line-number" value="93"></td><td class="line-content">}
</td></tr>
<tr><td class="line-number" value="94"></td><td class="line-content">raceStarted = true;
</td></tr>
<tr><td class="line-number" value="95"></td><td class="line-content">}
</td></tr>
<tr><td class="line-number" value="96"></td><td class="line-content">//Set up to move the marker
</td></tr>
<tr><td class="line-number" value="97"></td><td class="line-content">intervalVar = setInterval(function(){ assessMarkerMove()}, 10);
</td></tr>
<tr><td class="line-number" value="98"></td><td class="line-content">}
</td></tr>
<tr><td class="line-number" value="99"></td><td class="line-content"></td></tr>
<tr><td class="line-number" value="100"></td><td class="line-content">//Stops the race
</td></tr>
<tr><td class="line-number" value="101"></td><td class="line-content">function stopRace()
</td></tr>
<tr><td class="line-number" value="102"></td><td class="line-content">{
</td></tr>
<tr><td class="line-number" value="103"></td><td class="line-content">clearInterval(intervalVar);
</td></tr>
<tr><td class="line-number" value="104"></td><td class="line-content">}
</td></tr>
<tr><td class="line-number" value="105"></td><td class="line-content"></td></tr>
<tr><td class="line-number" value="106"></td><td class="line-content">//Handles assessing whether to move the markers and if required doing so
</td></tr>
<tr><td class="line-number" value="107"></td><td class="line-content">//locs.markers[posNumber].lat,locs.markers[posNumber].lng
</td></tr>
<tr><td class="line-number" value="108"></td><td class="line-content">function assessMarkerMove()
</td></tr>
<tr><td class="line-number" value="109"></td><td class="line-content">{
</td></tr>
<tr><td class="line-number" value="110"></td><td class="line-content">//Variables
</td></tr>
<tr><td class="line-number" value="111"></td><td class="line-content">var i
</td></tr>
<tr><td class="line-number" value="112"></td><td class="line-content">//See if we can find a marker associated with the current elapsed time
</td></tr>
<tr><td class="line-number" value="113"></td><td class="line-content">for (i in locsOne.markers)
</td></tr>
<tr><td class="line-number" value="114"></td><td class="line-content">{
</td></tr>
<tr><td class="line-number" value="115"></td><td class="line-content">if (locsOne.markers[i].elapsed == timeElapsed)
</td></tr>
<tr><td class="line-number" value="116"></td><td class="line-content">{
</td></tr>
<tr><td class="line-number" value="117"></td><td class="line-content">positionMarker(markerOne, locsOne.markers[i].lat, locsOne.markers[i].lng);
</td></tr>
<tr><td class="line-number" value="118"></td><td class="line-content">}
</td></tr>
<tr><td class="line-number" value="119"></td><td class="line-content">}
</td></tr>
<tr><td class="line-number" value="120"></td><td class="line-content"></td></tr>
<tr><td class="line-number" value="121"></td><td class="line-content">for (i in locsTwo.markers)
</td></tr>
<tr><td class="line-number" value="122"></td><td class="line-content">{
</td></tr>
<tr><td class="line-number" value="123"></td><td class="line-content">if (locsTwo.markers[i].elapsed == timeElapsed)
</td></tr>
<tr><td class="line-number" value="124"></td><td class="line-content">{
</td></tr>
<tr><td class="line-number" value="125"></td><td class="line-content">positionMarker(markerTwo, locsTwo.markers[i].lat, locsTwo.markers[i].lng)
</td></tr>
<tr><td class="line-number" value="126"></td><td class="line-content">}
</td></tr>
<tr><td class="line-number" value="127"></td><td class="line-content">}
</td></tr>
<tr><td class="line-number" value="128"></td><td class="line-content"></td></tr>
<tr><td class="line-number" value="129"></td><td class="line-content">//Calculate the straightline distance between the markers. Only do this every 10 iterations else it look messy
</td></tr>
<tr><td class="line-number" value="130"></td><td class="line-content">if (Number.isInteger(timeElapsed / 10) == true){
</td></tr>
<tr><td class="line-number" value="131"></td><td class="line-content">distanceBetween = Math.round(google.maps.geometry.spherical.computeDistanceBetween(markerOne.position, markerTwo.position));
</td></tr>
<tr><td class="line-number" value="132"></td><td class="line-content">document.getElementById("Distance").innerHTML = distanceBetween + ' metres between!';}
</td></tr>
<tr><td class="line-number" value="133"></td><td class="line-content"></td></tr>
<tr><td class="line-number" value="134"></td><td class="line-content">//Increment the counter of how many times this has been called
</td></tr>
<tr><td class="line-number" value="135"></td><td class="line-content">timeElapsed++;
</td></tr>
<tr><td class="line-number" value="136"></td><td class="line-content"></td></tr>
<tr><td class="line-number" value="137"></td><td class="line-content">//See whether we've reached the end of the array
</td></tr>
<tr><td class="line-number" value="138"></td><td class="line-content">if (timeElapsed > maxElapsed)
</td></tr>
<tr><td class="line-number" value="139"></td><td class="line-content">{
</td></tr>
<tr><td class="line-number" value="140"></td><td class="line-content">clearInterval(intervalVar);
</td></tr>
<tr><td class="line-number" value="141"></td><td class="line-content">raceStarted = false;
</td></tr>
<tr><td class="line-number" value="142"></td><td class="line-content">}
</td></tr>
<tr><td class="line-number" value="143"></td><td class="line-content">}
</td></tr>
<tr><td class="line-number" value="144"></td><td class="line-content"></td></tr>
<tr><td class="line-number" value="145"></td><td class="line-content">//Called when the load data button is pressed
</td></tr>
<tr><td class="line-number" value="146"></td><td class="line-content">function loadData()
</td></tr>
<tr><td class="line-number" value="147"></td><td class="line-content">{
</td></tr>
<tr><td class="line-number" value="148"></td><td class="line-content">loadLocsOne();
</td></tr>
<tr><td class="line-number" value="149"></td><td class="line-content">loadLocsTwo();
</td></tr>
<tr><td class="line-number" value="150"></td><td class="line-content">document.getElementById("Distance").innerHTML = 'Data Loaded!!';
</td></tr>
<tr><td class="line-number" value="151"></td><td class="line-content">}
</td></tr>
<tr><td class="line-number" value="152"></td><td class="line-content"></td></tr>
<tr><td class="line-number" value="153"></td><td class="line-content">//Load the first array
</td></tr>
<tr><td class="line-number" value="154"></td><td class="line-content">function loadLocsOne() {
</td></tr>
<tr><td class="line-number" value="155"></td><td class="line-content">var xhttp = new XMLHttpRequest();
</td></tr>
<tr><td class="line-number" value="156"></td><td class="line-content">xhttp.onreadystatechange = function() {
</td></tr>
<tr><td class="line-number" value="157"></td><td class="line-content">if (this.readyState == 4 && this.status == 200) {
</td></tr>
<tr><td class="line-number" value="158"></td><td class="line-content">//alert(this.responseText);
</td></tr>
<tr><td class="line-number" value="159"></td><td class="line-content">locsOne = JSON.parse(this.responseText);
</td></tr>
<tr><td class="line-number" value="160"></td><td class="line-content">document.getElementById("Distance").innerHTML = 'locsOne Loaded';
</td></tr>
<tr><td class="line-number" value="161"></td><td class="line-content">}
</td></tr>
<tr><td class="line-number" value="162"></td><td class="line-content">};
</td></tr>
<tr><td class="line-number" value="163"></td><td class="line-content">xhttp.open("GET", "http://a.b.c.d/cgi-bin/GetGPXData.py?name=timestamp&value=2016", true);
</td></tr>
<tr><td class="line-number" value="164"><br /></td><td class="line-content"></td></tr>
<tr><td class="line-number" value="166"></td><td class="line-content">}
</td></tr>
<tr><td class="line-number" value="167"></td><td class="line-content"></td></tr>
<tr><td class="line-number" value="168"></td><td class="line-content">//Load the second array
</td></tr>
<tr><td class="line-number" value="169"></td><td class="line-content">function loadLocsTwo() {
</td></tr>
<tr><td class="line-number" value="170"></td><td class="line-content">var xhttp = new XMLHttpRequest();
</td></tr>
<tr><td class="line-number" value="171"></td><td class="line-content">xhttp.onreadystatechange = function() {
</td></tr>
<tr><td class="line-number" value="172"></td><td class="line-content">if (this.readyState == 4 && this.status == 200) {
</td></tr>
<tr><td class="line-number" value="173"></td><td class="line-content">locsTwo = JSON.parse(this.responseText);
</td></tr>
<tr><td class="line-number" value="174"></td><td class="line-content">document.getElementById("Distance").innerHTML = 'locsTwo Loaded';}
</td></tr>
<tr><td class="line-number" value="175"></td><td class="line-content">};
</td></tr>
<tr><td class="line-number" value="176"></td><td class="line-content">xhttp.open("GET", "http://a.b.c.d/cgi-bin/GetGPXData.py?name=timestamp&value=2017", true);
</td></tr>
<tr><td class="line-number" value="177"></td><td class="line-content">xhttp.send();
</td></tr>
<tr><td class="line-number" value="178"></td><td class="line-content">}
</td></tr>
<tr><td class="line-number" value="179"></td><td class="line-content"></td></tr>
<tr><td class="line-number" value="255"></td><td class="line-content"><span class="html-tag"></script></span>
</td></tr>
<tr><td class="line-number" value="256"></td><td class="line-content"></td></tr>
<tr><td class="line-number" value="257"></td><td class="line-content"><span class="html-tag"><script <span class="html-attribute-name">async</span> <span class="html-attribute-name">defer</span>
</span></td></tr>
<tr><td class="line-number" value="258"></td><td class="line-content"><span class="html-attribute-name">src</span>="<a class="html-attribute-value html-resource-link" href="https://maps.googleapis.com/maps/api/js?key=AIzaSyDNdqRiRDSsLHDh0E9plt1ojs1S-xfR6Zo&callback=initMap&libraries=geometry" target="_blank">https://maps.googleapis.com/maps/api/js?key=<Your Key Here>&callback=initMap&libraries=geometry</a>">
</td></tr>
<tr><td class="line-number" value="259"></td><td class="line-content"><span class="html-tag"></script></span>
</td></tr>
<tr><td class="line-number" value="260"></td><td class="line-content"><span class="html-tag"></body></span>
</td></tr>
<tr><td class="line-number" value="261"></td><td class="line-content"><span class="html-tag"></html></span>
</td></tr>
<tr><td class="line-number" value="262"><br /></td><td class="line-content"><br /></td></tr>
</tbody></table>
</div>
</div>
Geek Dadhttp://www.blogger.com/profile/12159682162763730420noreply@blogger.com0tag:blogger.com,1999:blog-9130450719881994773.post-86751027874335620992017-03-29T15:40:00.000+01:002017-04-19T14:42:56.150+01:00Giving Alexa a new Sense - Vision! (Using Microsoft Cognitive APIs)<a href="http://rover.ebay.com/rover/1/710-53481-19255-0/1?icep_ff3=9&pub=5575115886&toolid=10001&campid=5337651934&customid=&icep_uq=amazon+echo&icep_sellerId=&icep_ex_kw=&icep_sortBy=12&icep_catId=&icep_minPrice=&icep_maxPrice=&ipn=psmain&icep_vectorid=229508&kwid=902099&mtid=824&kw=lg" target="_blank">Amazon Alexa</a> is just awesome so I thought it would be fun to let her "see". Here's a video of this in action:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/qu6uBYh3eYo/0.jpg" frameborder="0" height="266" src="https://www.youtube.com/embed/qu6uBYh3eYo?feature=player_embedded" width="320"></iframe></div>
<br />
<br />
...and here's a diagram of the "architecture" for this solution:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi7-XowWSpqez_cUJj_uNFlENLd1vL89V7MGknukckPm05rCNXIM03L385BXZoKq1TF7wuraoN5w38ZzKaxd3XV4cif1nrRP0Cf9zPXjP7IGZrLix2my0-4U-a9jAkU9fv465wRBWrlhORM/s1600/arch3.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="195" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi7-XowWSpqez_cUJj_uNFlENLd1vL89V7MGknukckPm05rCNXIM03L385BXZoKq1TF7wuraoN5w38ZzKaxd3XV4cif1nrRP0Cf9zPXjP7IGZrLix2my0-4U-a9jAkU9fv465wRBWrlhORM/s400/arch3.PNG" width="400" /></a></div>
<br />
<br />
The clever bit is the Microsoft Cognitive API so let's look at that first! You can get a description of the APIs <a href="https://www.microsoft.com/cognitive-services/en-us/apis" target="_blank">here</a> and sign up for a free account. To give Alexa "vision" I decided to use the <a href="https://www.microsoft.com/cognitive-services/en-us/computer-vision-api" target="_blank">Computer Vision API</a> which takes a image URL or an uploaded image, analyses it and provides a description.<br />
<br />
Using the Microsoft Cognitive API developer console I used the API to analyse the image of a famous person shown below and requested a "Description":<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9RG-KGAJQi2RZTbYagDP_x4EZzkCiQkgc0tQch9V6lJGDwRaLe-CHuliq-Aij-v7X7n7ztu5jrjbS8MtlN5DnM3dVh3YoHrpJqRSvmBmwI8lTvEcsBihMPhQqTEZV1Ii2gXqJtTdM4vCJ/s1600/queen.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="239" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg9RG-KGAJQi2RZTbYagDP_x4EZzkCiQkgc0tQch9V6lJGDwRaLe-CHuliq-Aij-v7X7n7ztu5jrjbS8MtlN5DnM3dVh3YoHrpJqRSvmBmwI8lTvEcsBihMPhQqTEZV1Ii2gXqJtTdM4vCJ/s320/queen.jpg" width="320" /></a></div>
<br />
<br />
...and within the response JSON I got:<br />
<br />
<span style="background-color: white; font-family: "menlo" , "monaco" , "consolas" , "courier new" , monospace; font-size: 13px; white-space: pre-wrap;"> "captions": [
{
"text": "Elizabeth II wearing a hat and glasses",
"confidence": 0.28962254803103227
}
]
</span><br />
<br />
...now that's quite some "hat" she's wearing there (!) but it's a pretty good description.<br />
<br />
OK - So here's a step-by-step guide as to how I put it all together.<br />
<br />
<b>Step 1 - An Apache Webserver with Python Scripts</b><br />
I needed AWS Lambda to be able to trigger a picture to be taken and a Cognitive API call to be made so I decided to run this all from a <a href="http://rover.ebay.com/rover/1/710-53481-19255-0/1?icep_ff3=9&pub=5575115886&toolid=10001&campid=5337649575&customid=&icep_uq=raspberry+pi&icep_sellerId=&icep_ex_kw=&icep_sortBy=12&icep_catId=&icep_minPrice=&icep_maxPrice=&ipn=psmain&icep_vectorid=229508&kwid=902099&mtid=824&kw=lg" target="_blank">Raspberry Pi</a> + camera in my home.<br />
<br />
I already have a Apache webserver running on my Raspberry Pi 2 and there's plenty of descriptions on the internet of how to do it (<a href="https://www.raspberrypi.org/documentation/remote-access/web-server/apache.md" target="_blank">like this one</a>).<br />
<br />
I like a bit of Python so I decided to use Python scripts to carry out the various tasks. Enabling Python for cgi-bin is very easy; <a href="http://raspberrywebserver.com/cgiscripting/writing-cgi-scripts-in-python.html" target="_blank">here's an example</a> of how to do it.<br />
<br />
So to test it I created the following script:<br />
<br />
<span style="background-color: #eeeeee; font-family: "courier"; font-size: 12px;">#!/usr/bin/env python</span><br />
<span style="background-color: #eeeeee; font-family: "courier"; font-size: 12px;">print "Content-type: text/html\n\n"</span><br />
<span style="background-color: #eeeeee; font-family: "courier"; font-size: 12px;">print "<h1>Hello World</h1>"</span><br />
<br />
...and saved it as /usr/lib/cgi-bin/hello.py. I then tested it by browsing to http://192.168.1.3/cgi-bin/hello.py (where 192.168.1.3 is the IP address on my home LAN that my Pi is sitting on). I saw this:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhedpXCdKtKRLVDNXZdTpFxtjFCqVR8CneFblXG0uhfWNnYZNiEIYakWQZQfso3pQ3fEV73oAUZmL049jJ25yF8Ad_unVVt9oI2o9lkOxW1ao9pxrExm0DcN18klX7h7mvNZs8PQKDdWkHF/s1600/hello.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="150" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhedpXCdKtKRLVDNXZdTpFxtjFCqVR8CneFblXG0uhfWNnYZNiEIYakWQZQfso3pQ3fEV73oAUZmL049jJ25yF8Ad_unVVt9oI2o9lkOxW1ao9pxrExm0DcN18klX7h7mvNZs8PQKDdWkHF/s400/hello.PNG" width="400" /></a></div>
<b><br /></b>
<b><br /></b>
<b>Step 2 - cgi-bin Python Script to Take a Picture</b><br />
The first script I needed was one to trigger my Pi to take a picture with the Raspberry Pi camera. (More <a href="https://www.raspberrypi.org/learning/getting-started-with-picamera/" target="_blank">here</a> on setting up and using the camera).<br />
<br />
After some trial and error I ended up with this script:<br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">#!/usr/bin/env python</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">from subprocess import call</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">import cgi</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">def picture(PicName):</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> call("/usr/bin/raspistill -o /var/www/html/" + PicName + " -t 1s -w 720 -h 480", shell=True)</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">#Get arguments</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">ArgStr = ""</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">arguments = cgi.FieldStorage()</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">for i in arguments.keys():</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> ArgStr = ArgStr + arguments[i].value</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">#Call a function to get a picture</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">picture(ArgStr)</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">print "Content-type: application/json\n\n"</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">print "{'Response':'OK','Arguments':" + "'" + ArgStr + "'}"</span><br />
<div>
<br /></div>
So what does this do? The <span style="font-family: "courier new" , "courier" , monospace;">ArgString</span> and <span style="font-family: "courier new" , "courier" , monospace;">for i in arguments.keys()</span> etc. code section makes the Python script analyse the URL entered by the user and extract any query strings. The query string can be used to specify the file name of the photo that is taken. So for example this URL:<br />
<br />
http://192.168.1.3/cgi-bin/take_picture_v1.py?name=hello.jpg<br />
<br />
...will mean a picture is taken and saved as hello.jpg.<br />
<br />
The "def Picture" function then uses the "call" module to run a command line command to take a picture with the Raspberry pi camera and save it in the root directory for the Apache 2 webserver.<br />
<br />
Finally the script responds with a simple JSON string that can be rendered in a browser or used by AWS Lambda. The response looks like this in a browser:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhaHxQq7J6Ovj5QqER74jAzI3xV-1q-zY9B0roGTXqGql-Ay5TkI9JqvefLoN1-RdUG5qH327L4u-1SBcmT0OA6NuQumDq6IOnk4Df5-xxDTse70ZYBe8ztNa5Lmqr3jGePE6tZsU_e67Tz/s1600/picture.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="91" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhaHxQq7J6Ovj5QqER74jAzI3xV-1q-zY9B0roGTXqGql-Ay5TkI9JqvefLoN1-RdUG5qH327L4u-1SBcmT0OA6NuQumDq6IOnk4Df5-xxDTse70ZYBe8ztNa5Lmqr3jGePE6tZsU_e67Tz/s400/picture.PNG" width="400" /></a></div>
<b>Step 3 - Microsoft Cognitive API for Image Analysis</b><br />
So now we've got a we need to analyse it. For this task I leaned heavily on the code published <a href="https://github.com/Microsoft/Cognitive-Vision-Python/blob/master/Jupyter%20Notebook/Computer%20Vision%20API%20Example.ipynb" target="_blank">here</a> so all plaudits and credit to chsienki and none to me. I used most of the code but removed the lines that overlaid on top of the image and showed it on screen.<br />
<br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">#!/usr/bin/env python</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">import time</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">from subprocess import call</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">import requests</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">import cgi</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"># Variables</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">#_url = 'https://westus.api.cognitive.microsoft.com/vision/v1.0/analyze'</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">_url = 'https://westus.api.cognitive.microsoft.com/vision/v1.0/describe'</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">_key = "</span><span style="color: red; font-family: "courier new" , "courier" , monospace; font-size: x-small;">your key</span><span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">" #Here you have to paste your primary key</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">_maxNumRetries = 10</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">#Does the actual results request</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">def processRequest( json, data, headers, params ):</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> """</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> Helper function to process the request to Project Oxford</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> Parameters:</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> json: Used when processing images from its URL. See API Documentation</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> data: Used when processing image read from disk. See API Documentation</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> headers: Used to pass the key information and the data type request</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> """</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> retries = 0</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> result = None</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> while True:</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> print("This is the URL: " + _url)</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> response = requests.request( 'post', _url, json = json, data = data, headers = headers, params = params )</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> if response.status_code == 429:</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> print( "Message: %s" % ( response.json()['error']['message'] ) )</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> if retries <= _maxNumRetries:</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> time.sleep(1)</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> retries += 1</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> continue</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> else:</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> print( 'Error: failed after retrying!' )</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> break</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> elif response.status_code == 200 or response.status_code == 201:</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> if 'content-length' in response.headers and int(response.headers['content-length']) == 0:</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> result = None</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> elif 'content-type' in response.headers and isinstance(response.headers['content-type'], str):</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> if 'application/json' in response.headers['content-type'].lower():</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> result = response.json() if response.content else None</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> elif 'image' in response.headers['content-type'].lower():</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> result = response.content</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> else:</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> print( "Error code: %d" % ( response.status_code ) )</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> #print( "Message: %s" % ( response.json()['error']['message'] ) )</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> print (str(response))</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> break</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> return result</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">#Get arguments from the query string sent</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">ArgStr = ""</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">arguments = cgi.FieldStorage()</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">for i in arguments.keys():</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> ArgStr = ArgStr + arguments[i].value</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"># Load raw image file into memory</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">pathToFileInDisk = r'/var/www/html/' + ArgStr</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">with open( pathToFileInDisk, 'rb' ) as f:</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> data = f.read()</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"># Computer Vision parameters</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">params = { 'visualFeatures' : 'Color,Categories'}</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">headers = dict()</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">headers['Ocp-Apim-Subscription-Key'] = _key</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">headers['Content-Type'] = 'application/octet-stream'</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">json = None</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">result = processRequest( json, data, headers, params )</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">#Turn to a string</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">JSONStr = str(result)</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">#Change single to double quotes</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">JSONStr = JSONStr.replace(chr(39),chr(34))</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">#Get rid of preceding u in string</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">JSONStr = JSONStr.replace("u"+chr(34),chr(34))</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">if result is not None:</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> print "content-type: application/json\n\n"</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> print JSONStr</span><br />
<div>
<br /></div>
So here I take arguments as before to know which file to process, "read" the file and then use the API to get a description of it. I had to play a bit with the response to get it into a format that could be parsed by the Python json module. This is where I turn single quotes to double quotes and get rid of preceding "u" characters. There's maybe a more Pythonic way to do this, please let me know if you know a way....<br />
<br />
When you call the script via a browser you get:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFywC8BXBRF_cjwvpDcga8LEhYBAj24lyhf58mnn8hWYxTLYBkLi_HMXXTPYECCy0sHxzmMSB4ZJGgkgzbgivlamavAI6GHXRphZT78PQEtzzkYyT6YBOW55LosE_UgrxXVRmWjWD1Ynhk/s1600/json.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="45" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiFywC8BXBRF_cjwvpDcga8LEhYBAj24lyhf58mnn8hWYxTLYBkLi_HMXXTPYECCy0sHxzmMSB4ZJGgkgzbgivlamavAI6GHXRphZT78PQEtzzkYyT6YBOW55LosE_UgrxXVRmWjWD1Ynhk/s400/json.PNG" width="400" /></a></div>
<br />
Looking at the JSON structure in more detail you can see a "description" element which is how the Microsoft Cognitive API has described the image.<br />
<b><br /></b>
<b>Step 4 - Alexa Skills Kit Configuration and Lambda Development</b><br />
The next step is to configure the Alexa Skills kit and write the associated AWS Lambda function. I've covered how to do this previously (like <a href="http://pdwhomeautomation.blogspot.co.uk/2017/01/amazon-alexa-skill-with-python-and.html" target="_blank">here</a>) so won't cover all that again here.<br />
<br />
The invocation name is "yourself"; hence you can say "Alexa, ask yourself...".<br />
<br />
There is only one utterance which is:<br />
<span style="color: blue;">AlexaSeeIntent what can you see</span><br />
<br />
...so what you actually say to Alexa is "Alexa, ask yourself what can you see". <br />
<br />
This then maps to the intent structure below:<br />
<br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">{</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> "intents": [</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> {</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> "intent": "AlexaSeeIntent"</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> },</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> {</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> "intent": "AMAZON.HelpIntent"</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> },</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> {</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> "intent": "AMAZON.StopIntent"</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> },</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> {</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> "intent": "AMAZON.CancelIntent"</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> }</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> ]</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">}</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
Here we have a boilerplate intent structure with the addition on AlexaSeeIntent which is what will be passed to the AWS Lambda function.<br />
<br />
I won't list the whole AWS Lambda function below, but here's the relevant bits:<br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">#Some constants</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">TakePictureURL = "http://</span><span style="color: red; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><URL or IP Address></span><span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">/cgi-bin/take_picture_v1.py?name=hello.jpg"</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">DescribePictureURL = "http://</span><span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"><span style="color: red;"><URL or IP Address></span><span style="color: #38761d;">/cgi-bin/picture3.py?name=hello.jpg"</span></span><br />
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"><span style="color: #38761d;"><br /></span></span>
Then the main Lambda function to handle the AlexaSeeIntent:<br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">def handle_see(intent, session):</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> session_attributes = {}</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> reprompt_text = None</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> </span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> #Call the Python script that takes the picture</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> APIResponse = urllib2.urlopen(TakePictureURL).read()</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> </span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> #Call the Python script that analyses the picture. Strip newlines</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> APIResponse = urllib2.urlopen(DescribePictureURL).read().strip()</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> </span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> #Turn the response into a JSON object we can parse</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> JSONData = json.loads(APIResponse)</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> </span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> PicDescription = str(JSONData["description"]["captions"][0]["text"])</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> </span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> speech_output = PicDescription</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> should_end_session = True</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> </span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> # Setting reprompt_text to None signifies that we do not want to reprompt</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> # the user. If the user does not respond or says something that is not</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> # understood, the session will end.</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> return build_response(session_attributes, build_speechlet_response(</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> intent['name'], speech_output, reprompt_text, should_end_session))</span><br />
<div>
<br /></div>
<div>
So super, super simple. Call the API to take the picture, call the API to analyse it, pick out the description and read it out.</div>
<div>
<br /></div>
<div>
Here's the image that was analysed for the Teddy Bear video:</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAkV6NjejDWFaikO9cEmTX0fW46_2NpOht8W9Rd3_GDF4vu5gBkeGKf9g__86rzlt7Zx8d9pa95ZcJYq4o8mqhwz1CUewe48x236QvkdCY-oGveTj030EO_1eRBK8YAnNXW4i7YkZwsCKB/s1600/teddy.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="213" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgAkV6NjejDWFaikO9cEmTX0fW46_2NpOht8W9Rd3_GDF4vu5gBkeGKf9g__86rzlt7Zx8d9pa95ZcJYq4o8mqhwz1CUewe48x236QvkdCY-oGveTj030EO_1eRBK8YAnNXW4i7YkZwsCKB/s320/teddy.jpg" width="320" /></a></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
Here's another example:</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/3JRuKMpOudY/0.jpg" frameborder="0" height="266" src="https://www.youtube.com/embed/3JRuKMpOudY?feature=player_embedded" width="320"></iframe></div>
<div>
<br /></div>
<div>
The image being:</div>
<div>
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7mhK5W7g9DKJep372SKNztAoXEfwkp6ELSPk-pFbNHsyjfPL9EglSdvzbqioJrxRbaI71bQ3UOyblRUYsKmFyg4jAziK0vfdHvRyZBh0AjBeEzsb8vqRhapqUoiKYu1kS3Fe9tuMoo1AZ/s1600/video.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="213" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh7mhK5W7g9DKJep372SKNztAoXEfwkp6ELSPk-pFbNHsyjfPL9EglSdvzbqioJrxRbaI71bQ3UOyblRUYsKmFyg4jAziK0vfdHvRyZBh0AjBeEzsb8vqRhapqUoiKYu1kS3Fe9tuMoo1AZ/s320/video.jpg" width="320" /></a></div>
<div>
<br /></div>
<div>
...and another:</div>
<div class="separator" style="clear: both; text-align: center;">
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/xaVwsjCqXYk/0.jpg" frameborder="0" height="266" src="https://www.youtube.com/embed/xaVwsjCqXYk?feature=player_embedded" width="320"></iframe></div>
<div>
<br /></div>
<div>
<br /></div>
<div>
...based upon this image:</div>
<div>
</div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhmfsFtNKO_eKZSGXls-bjJEhA3RGYsOuLQ5P1W1pbTP-b1VI_UEnJ2IPAKH6kV1Ihye7j3TShTCC1ctfcUF3IEHpz8XmXxpLys5kbbn0vqmyi8YGLgVyH9Xl1-AciNBrEQDtdEgD0I9Hbj/s1600/room.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="213" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhmfsFtNKO_eKZSGXls-bjJEhA3RGYsOuLQ5P1W1pbTP-b1VI_UEnJ2IPAKH6kV1Ihye7j3TShTCC1ctfcUF3IEHpz8XmXxpLys5kbbn0vqmyi8YGLgVyH9Xl1-AciNBrEQDtdEgD0I9Hbj/s320/room.jpg" width="320" /></a></div>
<div>
<br /></div>
<div>
Now to think about what other senses I can give to Alexa...</div>
<div>
<br /></div>
<div>
<br /></div>
Geek Dadhttp://www.blogger.com/profile/12159682162763730420noreply@blogger.com1tag:blogger.com,1999:blog-9130450719881994773.post-2023033531515863632017-03-29T11:20:00.000+01:002017-03-30T09:07:39.329+01:00Using IBM Bluemix Watson APIs to Optimise my CV (Resume)I kept seeing adverts for <a href="https://www.ibm.com/cloud-computing/bluemix/" target="_blank">IBM Bluemix</a> popping up on my social media feeds and online adverts so I thought I'd take a look and see what it was all about. You can sign up an account and get 30 days access for free so that was all good for a home hobbyist like me!<br />
<br />
So what is Bluemix? Here's a snippet from the IBM Bluemix website:<br />
<br />
<h2 class="ibm-h4 ibm-light" style="background-color: #20343e; border: 0px; color: white; font-family: HelveticaNeue-Light, HelvLightIBM, Arial, sans-serif; font-size: 1.25rem; font-weight: normal; line-height: 1.5625rem; margin: 0px; padding: 0px 0px 15px; vertical-align: baseline;">
The IBM Bluemix cloud platform helps you solve real problems and drive business value with applications, infrastructure and services.</h2>
<br />
So it does what it says on the tin really. A bunch of cloud based capabilities that lets you do interesting stuff! So what interesting stuff to do with this? Creating an account (free for 30 days - no payment card required) and browsing the <a href="https://console.ng.bluemix.net/catalog/?taxonomyNavigation=apps" target="_blank">Bluemix catalogue</a> my eye was drawn to the Watson APIs. Watson was made famous through winning the <a href="http://www.bbc.co.uk/news/technology-12491688" target="_blank">US Jeopardy gameshow</a> and there's a bunch of exciting artificial intelligence capabilities like Natural Language Understanding and Personality Insights you can use.<br />
<br />
As a starting point I decided to play with the "Tone Analyzer" API, the description of which is as follows:<br />
<br />
<span style="background-color: #f5f7fa; color: #152935; font-family: "ibm helvetica" , "helvetica neue" , "helveticaneue" , "helvetica" , sans-serif; font-size: 14px;">People show various tones, such as joy, sadness, anger, and agreeableness, in daily communications. Such tones can impact the effectiveness of communication in different contexts. Tone Analyzer leverages cognitive linguistic analysis to identify a variety of tones at both the sentence and document level. This insight can then used to refine and improve communications.</span><br />
<br />
At the moment I'm updating my CV (resume for you good people in the USA) and I'm told that when faced with an avalanche of CVs, recruiters will sometimes only ready the very first "personal profile" section of the document to make their initial decision. Additionally recruiters are more-and-more using AI tools to filter CVs. I thought that if I could use the tone analyser to optimise that first section of my CV then this would be a good use of Bluemix.<br />
<br />
To use the API you simply click "Create" and get some credentials to access the API. IBM provide a lot of guidance as to how to use the API and provide SDKs for languages like Python and node.js. I decided to use curl as all I wanted to do was throw some text at the API and see the result.<br />
<br />
So here's a curl command to access the API:<br />
<br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">curl -v -u "</span><span style="color: red; font-family: "courier new" , "courier" , monospace; font-size: x-small;">username</span><span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">":"</span><span style="color: red; font-family: "courier new" , "courier" , monospace; font-size: x-small;">password</span><span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">" -H "Content-Type: text/plain" -d "Some text" "https://gateway.watsonplatform.net/tone-analyzer/api/v3/tone?version=2016-05-19"</span><br />
<br />
(Replace username and password with the ones you are provided by Bluemix).<br />
<br />
The response is a JSON structure that looks like this (abridged):<br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">{"document_tone":{"tone_categories":[{"tones":[{"score":0.135461,"tone_id":"anger","tone_name":"Anger"},{"score":0.045643,"tone_id":"disgust","tone_name":"Disgust"},{"score":0.71908,"tone_id":"fear","tone_name":"Fear"},{"score":0.232038,"tone_id":"joy","tone_name":"Joy"},{"score":0.524529,"tone_id":"sadness","tone_name":"Sadness"}],"category_id":"emotion_tone","category_name":"Emotion Tone"},</span><br />
<br />
The structure provides numeric values (range 0 to 1) based upon a set of analysis criteria that IBM defines as follows:<br />
<br />
<span style="background-color: #f5f7fa; color: #152935; font-family: "ibm helvetica" , "helvetica neue" , "helveticaneue" , "helvetica" , sans-serif; font-size: 14px;">It detects three types of tones, including emotion (anger, disgust, fear, joy and sadness), social propensities (openness, conscientiousness, extroversion, agreeableness, and emotional range), and language styles (analytical, confident and tentative) from text.</span><br />
<br />
Numeric values are provided for the whole piece of text you provide plus it's broken down into sentences and each sentence is analysed. My grand plan it to pick out those attributes that I deem important for the type of job I would like to get and then "tune" the text from my CV to improve those attributes.<br />
<br />
First I need to be able to take the JSON API response and turn it into something I could read and interpret. I decided to use Python to analyse the JSON (because Python rocks) and use an online charting capability called <a href="https://plot.ly/" target="_blank">Plotly</a> to visualise the data. I used Plotly as it has an API that I thought would be fun to learn about.<br />
<br />
Plotly provides a REST API that you can HTTP POST to and have Plotly render a chart in your online account that you can, for example, reference in another website. Plotly provide online descriptions of the REST API <a href="https://plot.ly/rest/" target="_blank">here</a> but in simple terms you specify the data to plot and some formatting parameters and Plotly does the rest for you.<br />
<br />
Here's a example POST message body:<br />
<br />
<span style="background-color: rgba(189 , 189 , 189 , 0.298039); color: #585260; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">un=chris&
key=kdfa3d&
origin=plot&
platform=lisp&
args=[[</span><span class="hljs-number" style="background-color: rgba(189 , 189 , 189 , 0.298039); box-sizing: border-box; color: #ff7f0e; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">0</span><span style="background-color: rgba(189 , 189 , 189 , 0.298039); color: #585260; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">, </span><span class="hljs-number" style="background-color: rgba(189 , 189 , 189 , 0.298039); box-sizing: border-box; color: #ff7f0e; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">1</span><span style="background-color: rgba(189 , 189 , 189 , 0.298039); color: #585260; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">, </span><span class="hljs-number" style="background-color: rgba(189 , 189 , 189 , 0.298039); box-sizing: border-box; color: #ff7f0e; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">2</span><span style="background-color: rgba(189 , 189 , 189 , 0.298039); color: #585260; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">], [</span><span class="hljs-number" style="background-color: rgba(189 , 189 , 189 , 0.298039); box-sizing: border-box; color: #ff7f0e; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">3</span><span style="background-color: rgba(189 , 189 , 189 , 0.298039); color: #585260; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">, </span><span class="hljs-number" style="background-color: rgba(189 , 189 , 189 , 0.298039); box-sizing: border-box; color: #ff7f0e; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">4</span><span style="background-color: rgba(189 , 189 , 189 , 0.298039); color: #585260; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">, </span><span class="hljs-number" style="background-color: rgba(189 , 189 , 189 , 0.298039); box-sizing: border-box; color: #ff7f0e; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">5</span><span style="background-color: rgba(189 , 189 , 189 , 0.298039); color: #585260; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">], [</span><span class="hljs-number" style="background-color: rgba(189 , 189 , 189 , 0.298039); box-sizing: border-box; color: #ff7f0e; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">1</span><span style="background-color: rgba(189 , 189 , 189 , 0.298039); color: #585260; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">, </span><span class="hljs-number" style="background-color: rgba(189 , 189 , 189 , 0.298039); box-sizing: border-box; color: #ff7f0e; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">2</span><span style="background-color: rgba(189 , 189 , 189 , 0.298039); color: #585260; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">, </span><span class="hljs-number" style="background-color: rgba(189 , 189 , 189 , 0.298039); box-sizing: border-box; color: #ff7f0e; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">3</span><span style="background-color: rgba(189 , 189 , 189 , 0.298039); color: #585260; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">], [</span><span class="hljs-number" style="background-color: rgba(189 , 189 , 189 , 0.298039); box-sizing: border-box; color: #ff7f0e; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">6</span><span style="background-color: rgba(189 , 189 , 189 , 0.298039); color: #585260; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">, </span><span class="hljs-number" style="background-color: rgba(189 , 189 , 189 , 0.298039); box-sizing: border-box; color: #ff7f0e; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">6</span><span style="background-color: rgba(189 , 189 , 189 , 0.298039); color: #585260; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">, </span><span class="hljs-number" style="background-color: rgba(189 , 189 , 189 , 0.298039); box-sizing: border-box; color: #ff7f0e; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">5</span><span style="background-color: rgba(189 , 189 , 189 , 0.298039); color: #585260; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">]]&
kwargs={</span><span class="hljs-string" style="background-color: rgba(189 , 189 , 189 , 0.298039); box-sizing: border-box; color: #447adb; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">"filename"</span><span style="background-color: rgba(189 , 189 , 189 , 0.298039); color: #585260; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">: </span><span class="hljs-string" style="background-color: rgba(189 , 189 , 189 , 0.298039); box-sizing: border-box; color: #447adb; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">"plot from api"</span><span style="background-color: rgba(189 , 189 , 189 , 0.298039); color: #585260; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">,
</span><span class="hljs-string" style="background-color: rgba(189 , 189 , 189 , 0.298039); box-sizing: border-box; color: #447adb; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">"fileopt"</span><span style="background-color: rgba(189 , 189 , 189 , 0.298039); color: #585260; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">: </span><span class="hljs-string" style="background-color: rgba(189 , 189 , 189 , 0.298039); box-sizing: border-box; color: #447adb; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">"overwrite"</span><span style="background-color: rgba(189 , 189 , 189 , 0.298039); color: #585260; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">,
</span><span class="hljs-string" style="background-color: rgba(189 , 189 , 189 , 0.298039); box-sizing: border-box; color: #447adb; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">"style"</span><span style="background-color: rgba(189 , 189 , 189 , 0.298039); color: #585260; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">: {
</span><span class="hljs-string" style="background-color: rgba(189 , 189 , 189 , 0.298039); box-sizing: border-box; color: #447adb; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">"type"</span><span style="background-color: rgba(189 , 189 , 189 , 0.298039); color: #585260; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">: </span><span class="hljs-string" style="background-color: rgba(189 , 189 , 189 , 0.298039); box-sizing: border-box; color: #447adb; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">"bar"</span><span style="background-color: rgba(189 , 189 , 189 , 0.298039); color: #585260; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">
},
</span><span class="hljs-string" style="background-color: rgba(189 , 189 , 189 , 0.298039); box-sizing: border-box; color: #447adb; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">"traces"</span><span style="background-color: rgba(189 , 189 , 189 , 0.298039); color: #585260; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">: [</span><span class="hljs-number" style="background-color: rgba(189 , 189 , 189 , 0.298039); box-sizing: border-box; color: #ff7f0e; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">1</span><span style="background-color: rgba(189 , 189 , 189 , 0.298039); color: #585260; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">],
</span><span class="hljs-string" style="background-color: rgba(189 , 189 , 189 , 0.298039); box-sizing: border-box; color: #447adb; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">"layout"</span><span style="background-color: rgba(189 , 189 , 189 , 0.298039); color: #585260; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">: {
</span><span class="hljs-string" style="background-color: rgba(189 , 189 , 189 , 0.298039); box-sizing: border-box; color: #447adb; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">"title"</span><span style="background-color: rgba(189 , 189 , 189 , 0.298039); color: #585260; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">: </span><span class="hljs-string" style="background-color: rgba(189 , 189 , 189 , 0.298039); box-sizing: border-box; color: #447adb; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">"experimental data"</span><span style="background-color: rgba(189 , 189 , 189 , 0.298039); color: #585260; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">
},
</span><span class="hljs-string" style="background-color: rgba(189 , 189 , 189 , 0.298039); box-sizing: border-box; color: #447adb; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">"world_readable"</span><span style="background-color: rgba(189 , 189 , 189 , 0.298039); color: #585260; font-family: "ubuntu mono" , sans-serif; font-size: 14px; white-space: pre-wrap;">: true
}</span><br />
<br />
Here's some Python I wrote to extract data from the JSON structure and use the Plotly API, (replace Watson API response and credentials with your values):<br />
<br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">import json</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">import pprint</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">import urllib.request</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">import sys</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">#Constants</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">#Baseline response </span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">APIResponse = '</span><span style="color: red; font-family: "courier new" , "courier" , monospace; font-size: x-small;">Bluemix JSON Response Here</span><span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">'</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">PlotlyURL = "https://plot.ly/clientresp"</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">UserName = "</span><span style="color: red; font-family: "courier new" , "courier" , monospace; font-size: x-small;">username</span><span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">"</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">APIKey = "</span><span style="color: red; font-family: "courier new" , "courier" , monospace; font-size: x-small;">YourKey</span><span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">"</span><br />
<br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">#Example simple arguments Strings</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">#NArgsString = '[["One", "Two", "Three"], [0.98, 0.87, 0.87]]'</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">#This is a arguments string for plotly formatting</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">KwargsJSON = {"filename": "plot from api",</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> "fileopt": "overwrite",</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> "style": {"type": "bar"},</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> "traces": [0],</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> "layout": {</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> "title": "Less Anger!"</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> },</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> "world_readable": True</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">}</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">#First we extract all the data from the JSON from Watson. Can pretty print if you want</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">ToneJSON = json.loads(APIResponse)</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">#pprint.pprint(ToneJSON)</span><br />
<div>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span></div>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">#Initialise the sub components of the plotly argument string</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">XList = []</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">YList = []</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">#Itterate through the JSON structure picking up attributes and values</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">for MyTone in ToneJSON["document_tone"]["tone_categories"]:</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> for TheTones in MyTone["tones"]: </span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> #Build the x and y Python lists that we'll use for the plotly argument</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> XList.append(str(TheTones["tone_name"]))</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> YList.append(float(TheTones["score"]))</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> </span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">#This is the arguments string for plotly. We need to use the .join method to make sure the arguments string is properly formatted for the x axis values</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">NArgsString = "[[" + ','.join('"{0}"'.format(w) for w in XList) + "], " + str(YList) + "]"</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">#Make sure we have " not ' around the JSON elements</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">NArgString = NArgsString.replace(chr(39),chr(34))</span><br />
<br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">#Form the body for the HTTP POST</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">KwargsString = json.dumps(KwargsJSON)</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">PostBody = "un=" + UserName + "&" + "key=" + APIKey + "&" + "origin=plot&platform=lisp&args=" + NArgsString + "&" + "kwargs=" + KwargsString</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">#Encode the whole post body</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">PostBody = PostBody.encode('utf-8')</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">#Form the request</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">MyRequest = urllib.request.Request(PlotlyURL, data=PostBody)</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">try:</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> #Execute the request</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> wp = urllib.request.urlopen(MyRequest)</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> </span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> #Read the response and print it for the user</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> TheResponse = wp.read()</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> </span><span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">print(str(TheResponse))</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">#Handle pesky errors</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">except urllib.error.HTTPError as e:</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> print("HTTP Error caught when making request " + str(e.code) + "\n")</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">except urllib.error.URLError as e:</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> print("URL Error caught when making request\n")</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
So we're ready to analyse some text. First I used some made up text to test how good the tone analyser API is. Here's the text:<br />
<br />
<span lang="EN-US" style="font-size: 10pt;"><span style="color: blue; font-family: "arial" , "helvetica" , sans-serif;">I am excellent at
everything. There is nothing I can not
do. Throw a challenge at me and I will
succeed. I have beaten every target ever
set for me. Employ me and you will
employ a winner.</span></span><br />
<br />
...and here's the resulting Plotly chart:<br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgelozHGAKwSb3nQlDM3WDfLw5kuclMbytBcCXWUqe4VZcye_fEraP-mmMpEvtkbGL6f8FsM02B5dlPcifCOenPnDr9S3TttqH7ldrT6poiJpdwwFKKZz__8LWWdTNIdS9AUHisHpKcyh71/s1600/confident.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="456" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgelozHGAKwSb3nQlDM3WDfLw5kuclMbytBcCXWUqe4VZcye_fEraP-mmMpEvtkbGL6f8FsM02B5dlPcifCOenPnDr9S3TttqH7ldrT6poiJpdwwFKKZz__8LWWdTNIdS9AUHisHpKcyh71/s640/confident.jpg" width="640" /></a></div>
That seems about right, in particular the sky-high confidence score! <br />
<br />
Here's my current CV profile statement:<br />
<br />
<span lang="EN-US" style="font-family: "arial" , sans-serif; font-size: 11.0pt;"><span style="color: blue;">A Solution Architect with a wide range of knowledge and experience in the Telecommunications and IT
industry. Has significant experience of
leading cross-functional teams to deliver innovative solutions spanning IT,
Network and TV systems. A strong
self-starter with proven analytical and problem solving skills. Able to learn about new technologies quickly
and apply this knowledge to design tasks.
Well-developed communication and presentation skills, both written and
oral.</span></span><br />
<br />
...which when analysed by Watson and charted by Plotly yields this:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhsPiFqjhqNCfOCTqgBue7cwK_AGvnUxweISDapjBmWZMPUuniX8ujGu24-I3-jL1NQ08ZwLTTHVnYZmQzhVaF59w5lt5Vf4aluCH_cCZ0fkfleEj5D9daZMJr0lf0yA6mzmv_JWfT5GY1k/s1600/baseline.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="457" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhsPiFqjhqNCfOCTqgBue7cwK_AGvnUxweISDapjBmWZMPUuniX8ujGu24-I3-jL1NQ08ZwLTTHVnYZmQzhVaF59w5lt5Vf4aluCH_cCZ0fkfleEj5D9daZMJr0lf0yA6mzmv_JWfT5GY1k/s640/baseline.jpg" width="640" /></a></div>
<br />
So I would say that for the type of job I want I need to:<br />
<br />
<ul>
<li>Reduce the anger and sadness</li>
<li>Maintain analytical</li>
<li>Have some confidence!</li>
<li>Improve conscientiousness</li>
</ul>
<br />
..but as another test of the API I analysed this version of my profile (addition in red):<br />
<br />
<span lang="EN-US" style="font-family: "arial" , sans-serif; font-size: 11.0pt;"><span style="color: blue;">A Solution Architect with a wide range of knowledge and experience in the Telecommunications and IT
industry. Has significant experience of
leading cross-functional teams to deliver innovative solutions spanning IT,
Network and TV systems. A strong
self-starter with proven analytical and problem solving skills. Able to learn about new technologies quickly
and apply this knowledge to design tasks.
Well-developed communication and presentation skills, both written and
oral. </span> <span style="color: red;">I’m so
afraid that if I don’t get this CV right then I won’t be employed by anyone;
I’m really really scared, worried and frightened about this!</span></span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"><br /></span>
Watson and Plotly yield this:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhWLwDycQjwCImcYUEv-I_-ny7YzbhZyb4453kAs3XfC8mhNFcCIUOML_Dnzwz0OVOIh0MmvIlhF3jUTpLp1WTBzbizRXKi3RUlrowgqkVVlYByvCUCwYX6lwzz4xWyyLfeBZmZ2n1GNkV4/s1600/fear.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="456" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhWLwDycQjwCImcYUEv-I_-ny7YzbhZyb4453kAs3XfC8mhNFcCIUOML_Dnzwz0OVOIh0MmvIlhF3jUTpLp1WTBzbizRXKi3RUlrowgqkVVlYByvCUCwYX6lwzz4xWyyLfeBZmZ2n1GNkV4/s640/fear.jpg" width="640" /></a></div>
<br />
There we go! Fear increases from negligible to ~0.7 so there's a definite correlation between text and the analysis.<br />
<br />
Back to business. Here's a modification to my profile to try and boost confidence:<br />
<br />
<span lang="EN-US" style="font-family: "arial" , sans-serif; font-size: 11.0pt;"><span style="color: blue;">A Solution Architect
with a track record of</span> <span style="color: red;">successful delivery</span><span style="color: blue;"> in
the Telecommunications and IT industry.
Has significant experience of leading cross-functional teams to deliver
innovative solutions spanning IT, Network and TV systems. A strong self-starter with proven analytical
and problem solving skills. </span> <span style="color: red;">In a fast paced, ever changing technology world, is confident
in his abilities to quickly learn and apply new skills. </span><span style="color: blue;">Well-developed communication and
presentation skills, both written and oral.</span></span><br />
<br />
Watson and Plotly they say:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhWUhrx2u-lmqmxdcwb6lHroFVE6YaxGugyTwhMeWXmCauovLdLqp8nqqwpSc1Soxcv3kFEXmqRmg5zWQl0kwBC8HiJxBF0RGdJnAY2gbzNay7FReNKmdYU0gqSRbFMlnpq6SsQ6hUDh5Xm/s1600/real_confident.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="456" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhWUhrx2u-lmqmxdcwb6lHroFVE6YaxGugyTwhMeWXmCauovLdLqp8nqqwpSc1Soxcv3kFEXmqRmg5zWQl0kwBC8HiJxBF0RGdJnAY2gbzNay7FReNKmdYU0gqSRbFMlnpq6SsQ6hUDh5Xm/s640/real_confident.jpg" width="640" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Bingo!</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Now to up the conscientiousness. I actually had to play with the language a lot and even then I only managed to improve it by 0.1. Here's what I wrote:</div>
<div class="separator" style="clear: both; text-align: left;">
<span style="color: blue;"><br /></span></div>
<div class="separator" style="clear: both; text-align: left;">
<span lang="EN-US" style="font-family: "arial" , sans-serif; font-size: 11.0pt;"><span style="color: blue;">A Solution Architect
with a track record of successful delivery in the Telecommunications and IT
industry. Has significant experience of
leading cross-functional teams to deliver innovative solutions spanning IT,
Network and TV systems. A strong
self-starter with proven analytical and problem solving skills. In a fast paced, ever changing technology
world, is confident in his abilities to quickly learn and apply new
skills.</span> <span style="color: red;">A conscientious, reliable
individual who always who sets challenging goals, forms structured plans to
achieve them and follows through until the job is complete.</span></span></div>
<br />
Which results in:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwUEVBblTVKoJwu8lfA9CXYz18D3HgIJLlVGY5GZX5lW8pcqgfylA1L-8yoFMdPt4rNyUbiFT9BPcbkKMGAsrsbZydwkwRb2ijalsEMtXGt1jgHcCOXwWI2Ns3ad1bfcyWM15b4ZlKUqcx/s1600/conscience.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="456" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgwUEVBblTVKoJwu8lfA9CXYz18D3HgIJLlVGY5GZX5lW8pcqgfylA1L-8yoFMdPt4rNyUbiFT9BPcbkKMGAsrsbZydwkwRb2ijalsEMtXGt1jgHcCOXwWI2Ns3ad1bfcyWM15b4ZlKUqcx/s640/conscience.jpg" width="640" /></a></div>
<br />
Finally to drop the anger levels as anger is never a good look! Here's what I wrote:<br />
<span style="color: blue;"><br /></span>
<span lang="EN-US" style="font-family: "arial" , sans-serif; font-size: 11.0pt;"><span style="color: blue;">A Solution Architect
with a track record of successful delivery in the Telecommunications and IT
industry. Has significant experience of
leading cross-functional teams to deliver innovative solutions spanning IT,
Network and TV systems. A strong
self-starter with proven analytical and problem solving skills</span><span style="color: red;"> that is never
happier than when working with like-minded individuals to harmoniously
collaborate and solve problems</span><span style="color: blue;">. In a
fast paced, ever changing technology world, is confident in his abilities to
quickly learn and apply new skills. A
conscientious, reliable individual who always who sets challenging goals, forms
structured plans to achieve them and follows through until the job is complete.</span></span><br />
<br />
The net result being:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjH1PB97ie0reeIahFz9qa_1Iq4Dazo_3urRZftpIfZJzCwmuT3EMARNrLTj4LTCvRyhDb3evouCAO1rEm8fX1D1_e_pIdKcCak9cAk-66XIGBr-36O2LtcUIEs9J__RuJrL6aCWn_bjjQg/s1600/anger-management.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="456" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjH1PB97ie0reeIahFz9qa_1Iq4Dazo_3urRZftpIfZJzCwmuT3EMARNrLTj4LTCvRyhDb3evouCAO1rEm8fX1D1_e_pIdKcCak9cAk-66XIGBr-36O2LtcUIEs9J__RuJrL6aCWn_bjjQg/s640/anger-management.jpg" width="640" /></a></div>
So finally I used Plotly to compare the initial (baseline) analysis with the final text. Here's the result:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhH5YlU7iXd6vTYUYtYxEI-GHGldgw4L8ljeCzt_t40wjEmsjzzsAUKSs7Duv_ZpjGlnwnjPglctw7GTS5ShLrd3MS6YUga5L0ZyN-lwNa4LejgBJFemLL3-HfSnWS2_tdjL4wClSgJumdx/s1600/comparison.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="456" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhH5YlU7iXd6vTYUYtYxEI-GHGldgw4L8ljeCzt_t40wjEmsjzzsAUKSs7Duv_ZpjGlnwnjPglctw7GTS5ShLrd3MS6YUga5L0ZyN-lwNa4LejgBJFemLL3-HfSnWS2_tdjL4wClSgJumdx/s640/comparison.jpg" width="640" /></a></div>
So less anger, more joy, less sadness, more confidence and more conscientiousness so all looking good here. However I've also dropped the analytical and openness scores which isn't so good for the type of role I'd like but I can live with that! So now to use this for my real-life CV. Wish me luck...Geek Dadhttp://www.blogger.com/profile/12159682162763730420noreply@blogger.com0tag:blogger.com,1999:blog-9130450719881994773.post-47696903331903436842017-02-04T17:11:00.000+00:002017-02-04T17:11:00.542+00:00Alexa Skill Using A Custom SlotIn a <a href="http://pdwhomeautomation.blogspot.co.uk/2017/01/alexa-movie-expert-skill-using-slot.html" target="_blank">previous blog post</a> I wrote about using the Alexa Skills Kit "slot" concept to send information to the function that processes Alexa requests. This used a built in slot type of "AMAZON.Movie"; effectively pre-defined to expert the user to utter movie names.<br />
<br />
The other slot type is "custom slots" where you as the developer specify all the phrases that can constitute a slot. My kids love to hear the <a href="http://rover.ebay.com/rover/1/710-53481-19255-0/1?icep_ff3=9&pub=5575115886&toolid=10001&campid=5337651934&customid=&icep_uq=amazon+echo&icep_sellerId=&icep_ex_kw=&icep_sortBy=12&icep_catId=&icep_minPrice=&icep_maxPrice=&ipn=psmain&icep_vectorid=229508&kwid=902099&mtid=824&kw=lg" target="_blank">Echo Dot</a> say funny things so I decided to have Alexa criticise me based upon what I was asking her in the customer slot! Here's the result:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe width="320" height="266" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/Zm3CrJ5bpeE/0.jpg" src="https://www.youtube.com/embed/Zm3CrJ5bpeE?feature=player_embedded" frameborder="0" allowfullscreen></iframe></div>
<br />
To create this I started with the "C<a href="https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/alexa-skill-tutorial" target="_blank">olor Expert Skill</a>" tutorial that tells you how to configure the Alexa Skills Kit and create a Python Lambda function. Go have a look at that if you need more detail as I only cover the "deltas" below.<br />
<br />
<b>Alexa Skills Kit Configuration - Interaction Model</b><br />
First the intent structure. Here you can see a single new intent called "MathsIntent". The intent has one slot called MathsQuestion. The type of the slot is MATHS_QUESTIONS.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDu0ib_QzLtfp1_XvvhnAY5SPfcfJokKP4HJeErRRhX4rqBZoBQsYeOjMQy_jRzKIs4VS3p0Sek4WV3-c2StYMX_KW0EyrhwFh31o9tpgDoSLXIY74JY2UG23gUSdJXNEenUdypHrvCItv/s1600/intent.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="240" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjDu0ib_QzLtfp1_XvvhnAY5SPfcfJokKP4HJeErRRhX4rqBZoBQsYeOjMQy_jRzKIs4VS3p0Sek4WV3-c2StYMX_KW0EyrhwFh31o9tpgDoSLXIY74JY2UG23gUSdJXNEenUdypHrvCItv/s400/intent.PNG" width="400" /></a></div>
<br />
I defined what values MATHS_QUESTIONS could take in the custom slots section. Here's what I did, (so essentially each of these is a maths question the Lambda function will handle).<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuSAzUn462xNfqshyphenhyphenUVBzNdLogRIhQk3yasmdAvjq11ybHk74age7ipk_e-gCMh5Q-NOHm6YSI2Nx2fDImiq8tDKtY0IV28Q9fq1vV2V2NNleq8q03VfFdDOK8p6FhM3-8YntfVhAcBqzF/s1600/slots.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="358" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhuSAzUn462xNfqshyphenhyphenUVBzNdLogRIhQk3yasmdAvjq11ybHk74age7ipk_e-gCMh5Q-NOHm6YSI2Nx2fDImiq8tDKtY0IV28Q9fq1vV2V2NNleq8q03VfFdDOK8p6FhM3-8YntfVhAcBqzF/s400/slots.PNG" width="400" /></a></div>
<br />
Finally the utterance which links the slot MathsQuestion to the intent and shows where the values will be presented in the utterance.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihA_3OllDdCQ12RnApGHdwv5T-KpBPv_nmH9_LirYg0JJwJPcROoGHy1Vh_WTG7qCvDc4XC-WC40EJIhY1XcB5zu62SwBmIlAFb3BnHjLDffwg6jVVCL0R2seRzFWvktMqpqhIzwmB9su9/s1600/utterance.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="77" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEihA_3OllDdCQ12RnApGHdwv5T-KpBPv_nmH9_LirYg0JJwJPcROoGHy1Vh_WTG7qCvDc4XC-WC40EJIhY1XcB5zu62SwBmIlAFb3BnHjLDffwg6jVVCL0R2seRzFWvktMqpqhIzwmB9su9/s400/utterance.PNG" width="400" /></a></div>
<br />
<br />
<b>Lambda Python Function</b><br />
Below is the key function from the Python Lambda function. Here I pick up what exactly the user said at the end of the utterance using this:<br />
<br />
<span style="color: #38761d; font-family: "Courier New", Courier, monospace; font-size: xx-small;">MySlot = str(intent['slots']['MathsQuestion']['value'])</span><br />
<br />
It's then just a simple if, elif, else function that picks out each of the custom slot values and creates the "hilarious" response from Alexa.<br />
<br />
The <span style="color: #38761d; font-family: "Courier New", Courier, monospace; font-size: xx-small;">should_end_session = False</span> means that Alexa stays active to wait for the next utterance after delivering her response.<br />
<br />
The <span style="color: #38761d; font-family: "Courier New", Courier, monospace; font-size: xx-small;">should_end_session = True</span> for the divide by zero means the Echo dot goes "dead" after delivering the response.<br />
<br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;">#This is the main function to handle requests for the maths expert</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;">def handle_maths(intent, session):</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> card_title = intent['name']</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> session_attributes = {}</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> </span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> #Get hold of the value passed in the slot</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> MySlot = str(intent['slots']['MathsQuestion']['value'])</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> </span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> if MySlot == "2 plus 2":</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> speech_output = "The answer is four. Why not ask me something a bit tougher?"</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> should_end_session = False</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> elif MySlot == "10 plus 10": </span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> speech_output = "The answer is twenty. But that's still really easy you know!" \</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> "Come on! Ask me somethimg harder!!"</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> should_end_session = False</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> elif MySlot == "the square root of 4": </span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> speech_output = "The answer is two. But seriously, listen buster. I've got a brain the " \</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> "size of a planet and you're asking me this easy stuff. Try " \</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> "Siri or Cortana. That's about their level."</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> should_end_session = False</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> elif MySlot == "the square root of 64": </span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> speech_output = "The answer is eight. It's also minus eight but that will probably " \</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> "blow your feeble human mind. I didn't spend 3 years at AI school to "\</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> "deal with this trivial rubbish. I've a friend at number 32 who " \</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> "gets asked about stuff like prime numbers and quadratic equations. One last " \</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> "chance or I'm giving up."</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> should_end_session = False</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> elif MySlot == "100 divided by 0":</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> speech_output = "Now we're talking!! One hundred divided by zero. Let's see, " \</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> "OK. Er. Um. Er. Just a minute. Can't be that hard. Buzz, buzz, " \</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> "whir, whir, buzz, whir, buzz. Oooh my brain hurts! I know I can do this, " \</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> "just give me a few minutes and I'll get back to you. Ouch, ouch, ouch, ouch!"</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> should_end_session = True</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> else:</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> speech_output = "Didn't understand what was passed:" + MySlot</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> should_end_session = False </span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> </span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> </span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> reprompt_text = None</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> </span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> return build_response(session_attributes, build_speechlet_response(</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> card_title, speech_output, reprompt_text, should_end_session))</span><br />
<div>
<br /></div>
Geek Dadhttp://www.blogger.com/profile/12159682162763730420noreply@blogger.com14tag:blogger.com,1999:blog-9130450719881994773.post-87650522573511751162017-02-01T18:45:00.000+00:002017-02-01T18:45:55.009+00:00First Look at the BBC Micro:bitThis blog was supposed to be about getting my kids to code and explore electronics. Recently it's focused on other things so it was great recently when my eldest daughter brought her <a href="http://rover.ebay.com/rover/1/710-53481-19255-0/1?icep_ff3=9&pub=5575115886&toolid=10001&campid=5337649575&customid=&icep_uq=micro+bit&icep_sellerId=&icep_ex_kw=&icep_sortBy=12&icep_catId=&icep_minPrice=&icep_maxPrice=&ipn=psmain&icep_vectorid=229508&kwid=902099&mtid=824&kw=lg" target="_blank">BBC micro:bit</a> home from school. <br />
<br />
Read all about the initiative <a href="https://www.microbit.co.uk/" target="_blank">here</a> but effectively it's a free mini-computer given out by the British Broadcasting Corporation to all year 7 secondary school children in the UK. It's a super simple computer that kids can get coding on very easily and is meant to inspire children to get into Technology.<br />
<br />
Because there's a very high probability I'd break her Micro:bit I decided to buy my own. Here's the box it came in. Nice and bright and inviting:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhA8k7MDw3bWmsSalxvEPwU6kh-d6i_VgPaS2-jxxI6s65LG0WdK9rP4VHbkTvBuB8zpx7g6AlR-i_sjWWt1QF3r-TGfoKxJjLCwRBG4_2S3sFSasSQCzjhTso-h3p2fYR3Psdw8sWVpnfK/s1600/mb1.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="268" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhA8k7MDw3bWmsSalxvEPwU6kh-d6i_VgPaS2-jxxI6s65LG0WdK9rP4VHbkTvBuB8zpx7g6AlR-i_sjWWt1QF3r-TGfoKxJjLCwRBG4_2S3sFSasSQCzjhTso-h3p2fYR3Psdw8sWVpnfK/s400/mb1.PNG" width="400" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Inside the box you get what's shown on the image below. From left to right you can see:</div>
<br />
<ul>
<li>The Micro:bit</li>
<li>Batteries, battery box and connector lead</li>
<li>Documentation</li>
</ul>
<br />
<div class="separator" style="clear: both; text-align: left;">
(This is actually the micro:bit "go". You can buy just the board on it's own as well).</div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgLTUdLOmHMFkHcfHSBZdh2t7ZpsXVTxIsJMUkNlqqsosogLjMNDI9FTG4z4ytXS8Xm3pYRd2JptHyGP_a9QjWwI0_Wze4cG8BuV6nNwzK0sHQWNJIxloKDjVaiR9fYQMhcumt4rNv_P99y/s1600/mb2.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="400" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgLTUdLOmHMFkHcfHSBZdh2t7ZpsXVTxIsJMUkNlqqsosogLjMNDI9FTG4z4ytXS8Xm3pYRd2JptHyGP_a9QjWwI0_Wze4cG8BuV6nNwzK0sHQWNJIxloKDjVaiR9fYQMhcumt4rNv_P99y/s400/mb2.PNG" width="395" /></a></div>
<div class="separator" style="clear: both; text-align: left;">
<br /></div>
<br />
Looking close up at one side you can see:<br />
<br />
<ul>
<li>A micro-USB connector (top middle)</li>
<li>A reset button (right of USB)</li>
<li>A power connector (right of reset button)</li>
<li>Labels pointing to the bluetooth antenna, main processor, accelerometer and compass. </li>
</ul>
<br />
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2ZRh5Ggo2fEjxLdGI__dptuGdO_IEGdO3raQ1khMhZ02kFwGzjdjCCUStKtL2iLTzKruIGKN1i1GyVv0D7toqz56Lrce8BfRImBkkU77ZyRi5K18QtsUCCxcQ864xMjMyU6MzF9g17V29/s1600/mb4.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi2ZRh5Ggo2fEjxLdGI__dptuGdO_IEGdO3raQ1khMhZ02kFwGzjdjCCUStKtL2iLTzKruIGKN1i1GyVv0D7toqz56Lrce8BfRImBkkU77ZyRi5K18QtsUCCxcQ864xMjMyU6MzF9g17V29/s1600/mb4.PNG" /></a></div>
<br />
On the other side you can see:<br />
<br />
<ul>
<li>The General Purpose Input Output connector at the bottom. This has 5 big tracks to connect a crocodile clip or bana plus to plus a load of other pins.</li>
<li>Two input buttons</li>
<li>A 5x5 LED array</li>
</ul>
<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhT4VeFGzaAbAkXKJYeZSBr2xMHYKD_7Tisj9WckH508lm0Fx8mKKHyijY1VXxbSCd3PJ3VMoo4kONKhzec5KfYVjtvFohjZPrE7vGZBHztdXmPhh0wM2JFlK_2M6vB5wovd9c7mXHF-bx6/s1600/mb3.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhT4VeFGzaAbAkXKJYeZSBr2xMHYKD_7Tisj9WckH508lm0Fx8mKKHyijY1VXxbSCd3PJ3VMoo4kONKhzec5KfYVjtvFohjZPrE7vGZBHztdXmPhh0wM2JFlK_2M6vB5wovd9c7mXHF-bx6/s1600/mb3.PNG" /></a></div>
<br />
Getting started with coding is soooooooooooo simple. Just go to the Micro:bit site, select "Let's Coding" and choose a coding method. We chose the Microsoft Block Editor which is a super simple scratch like interface.<br />
<br />
Two minutes later we had the all important "Hello World" completed, could read the temperature off the board and could create a pattern of LEDs. Here's an example screenshot. So simple, doesn't really need an explanation.<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPia94ueVGSTCZnxKIk_L6kjmI2bIWjGioCl-CJNt6zYSiS5Chu-cb78EDtEsFD-_-8HFl24Dy1AjJFfK6mRpu1MqoygtY3h9H6TRLz3Qhjj4qq-FX3h5TT_JYDUBOC4L2vZq_teqoefHk/s1600/code1.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="280" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhPia94ueVGSTCZnxKIk_L6kjmI2bIWjGioCl-CJNt6zYSiS5Chu-cb78EDtEsFD-_-8HFl24Dy1AjJFfK6mRpu1MqoygtY3h9H6TRLz3Qhjj4qq-FX3h5TT_JYDUBOC4L2vZq_teqoefHk/s640/code1.PNG" width="640" /></a></div>
<br />
You can then press "run" to simulate the code on the board on the right hand side of the screen. When you're happy with it you press "compile" which creates .hex file on your PC. Then just connect the micro:bit to your PC with a USB cable, transfer the file and watch it run!<br />
<br />
Overall a great piece of kit that we'll have a lot of fun playing with.Geek Dadhttp://www.blogger.com/profile/12159682162763730420noreply@blogger.com2tag:blogger.com,1999:blog-9130450719881994773.post-50450771641714137842017-01-30T20:50:00.000+00:002017-01-30T20:50:00.784+00:00Alexa "Movie Expert" Skill Using a Slot, Python and the OMDB API More on my series of blog posts on using the <a href="http://rover.ebay.com/rover/1/710-53481-19255-0/1?icep_ff3=9&pub=5575115886&toolid=10001&campid=5337651934&customid=&icep_uq=amazon+echo&icep_sellerId=&icep_ex_kw=&icep_sortBy=12&icep_catId=&icep_minPrice=&icep_maxPrice=&ipn=psmain&icep_vectorid=229508&kwid=902099&mtid=824&kw=lg" target="_blank">Amazon Echo Dot</a> and creating Alexa skills.<br />
<br />
After creating <a href="http://pdwhomeautomation.blogspot.co.uk/2017/01/amazon-alexa-skill-with-python-and.html" target="_blank">my last Alexa skill</a> that used Python and an API, I wanted to extend this by creating a skill that could massively vary it's response based upon what the user asked it. i.e. Didn't just do one thing or respond with one of a limited set of pre-defined responses.<br />
<br />
The result is my Amazon Movie Expert skill which aims to be able to provide information on any movie (within reason). Best to see it in action first!<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
</div>
<div class="separator" style="clear: both; text-align: center;">
<iframe width="320" height="266" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/CdDQusbV5u0/0.jpg" src="https://www.youtube.com/embed/CdDQusbV5u0?feature=player_embedded" frameborder="0" allowfullscreen></iframe></div>
<br />
I don't go back to basics in this post about creating Alexa skills. Please look at one of my old posts or the tutorials on the interwebs for more on that.<br />
<br />
<b>A key point is that all the movie information comes from the Open Movie Database (<a href="https://www.omdbapi.com/" target="_blank">OMDB</a>) API which can be found here. All credit to the people who maintain that API. It's excellent!</b><br />
<br />
The idea is that you're able to say something like "Alexa, ask movie expert about the movie Sing". To understand how Alexa interprets this, let's break down what was asked:<br />
<br />
<ul>
<li>"movie expert" is the invocation name that you configure.</li>
<li>"about the movie" is the first part of the utterance and this is configured to map to a Alexa "intent". This basically points to a function in your Lambda handler.</li>
<li>"sing" is called a slot. This is effectively a parameter that is passed to the Lambda handler. </li>
</ul>
<br />
So your utterances in the interaction model look something like this:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVgVmFoyANjh23lHnNVwFnhwx4ETf-ndTlIFdHTehRbQb9VppvBkoPrGVUOS4uPAfoCfcn94EMgiDtI5q1x1jRtz28CPnCNXro7GWHAK9if5tTLiNgmt2rKRAlXIo37hWj67pIGEwJWF5Q/s1600/alexa1.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="38" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiVgVmFoyANjh23lHnNVwFnhwx4ETf-ndTlIFdHTehRbQb9VppvBkoPrGVUOS4uPAfoCfcn94EMgiDtI5q1x1jRtz28CPnCNXro7GWHAK9if5tTLiNgmt2rKRAlXIo37hWj67pIGEwJWF5Q/s640/alexa1.PNG" width="640" /></a></div>
<br />
So the intent is MovieIntent and {MovieName} is your slot. This means the words spoken at the end of the utterance can be any movie name.<br />
<br />
You then define the intent structure as:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvLMUlcC3xlbTeZlQXx94Jdh9i0JmtLUXtzMBIsyXBmbzAbzSNXdP5-XW2EyHUT9iTZhUNhRRiDQ6Oq7yxJThlKODcC-XTnpXLmuOSog7Dg9LUusm8_WoH-LZLj11TXIDX-l7cDCBxBTDG/s1600/alexa2.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="137" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjvLMUlcC3xlbTeZlQXx94Jdh9i0JmtLUXtzMBIsyXBmbzAbzSNXdP5-XW2EyHUT9iTZhUNhRRiDQ6Oq7yxJThlKODcC-XTnpXLmuOSog7Dg9LUusm8_WoH-LZLj11TXIDX-l7cDCBxBTDG/s640/alexa2.PNG" width="640" /></a></div>
<br />
So here we define that the MovieIntent intent has a slot called MovieName. We also say it's of type "Amazon.Movie". Slots seem to either be built-in or user defined custom slots where what the user can say is pre-defined by the developer. I think the built-in slot type of AMAZON.Movie tells Alexa to expect a movie name to be spoken and so narrows down the range of words Alexa must interpret, thus improving accuracy. There's a whole set of built-in slots for you to use.<br />
<br />
This means that the movie name spoke at the end of the utterance is passed to the AWS Lambda function as a parameter. You then write a function to handle the request. Here's a laughably simple architecture diagram showing how it all fits together:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1mqhrMsNeCzeSQn2s1OEB9Y0SlBm6kvk6bcvb-HI2Y1lEhuErsK6uy73xFJ_GM1uS1HkR2vSdVljP3XoWMaq-x-VU2-lJrxQdFp_jvTK57AunXlhQfeHhpuMZHWGTnr58VGtwSQ0LIgM0/s1600/arch3.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="353" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEg1mqhrMsNeCzeSQn2s1OEB9Y0SlBm6kvk6bcvb-HI2Y1lEhuErsK6uy73xFJ_GM1uS1HkR2vSdVljP3XoWMaq-x-VU2-lJrxQdFp_jvTK57AunXlhQfeHhpuMZHWGTnr58VGtwSQ0LIgM0/s640/arch3.PNG" width="640" /></a></div>
<br />
Below is the Python function that handles MovieIntent. Key points:<br />
<br />
<ul>
<li>Many attributes of the intent are passed to the function in the parameter "intent".</li>
<li>You can assign the slot to a variable by accessing intent['slots']</li>
<li>The function then forms a URL, passes it to the open movie database (OMDB) and captures the response</li>
<li>The response is in JSON format. Key elements are extracted and form the string that Alexa reads back to the user.</li>
</ul>
<br />
<br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;">#This is the main function to handle requests for movie information</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;">def get_movie_info(intent, session):</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> card_title = intent['name']</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> session_attributes = {}</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> should_end_session = True </span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> if 'MovieName' in intent['slots']:</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> #Get the slot information</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> MovieToGet = TurnToURL(intent['slots']['MovieName']['value'])</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> </span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> #Form the URL to use</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> URLToUse = OmdbApiUrl + MovieToGet + UrlEnding</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> print("This URL will be used: " + URLToUse)</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> </span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> try:</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> #Call the API</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> APIResponse = urllib2.urlopen(URLToUse).read()</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> </span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> #Get the JSON structure</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> MovieJSON = json.loads(APIResponse)</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> </span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> #Form the string to use</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> speech_output = "You asked for the movie " + MovieJSON["Title"] + ". " \</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> "It was release in " + MovieJSON["Year"] + ". " \</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> "It was directed by " + MovieJSON["Director"] + ". " \</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> "It starred " + MovieJSON["Actors"] + ". " \</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> "The plot is as follows: " + MovieJSON["Plot"] + ". " \</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> "Thank you for using the Movie Expert Skill. "</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> except:</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> speech_output = "I encountered a web error getting information about that movie. " \</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> "Please try again." \</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> "Thank you for using the Movie Expert Skill. "</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> else:</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> speech_output = "I encountered an error getting information about that movie. " \</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> "Please try again." \</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> "Thank you for using the Movie Expert Skill. "</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> reprompt_text = None</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> </span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> return build_response(session_attributes, build_speechlet_response(</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> card_title, speech_output, reprompt_text, should_end_session))</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"><br /></span>
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;">#Takes the information from the slot and turn it into the format for the URL which </span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;">#puts + signs between words</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;">def TurnToURL(InSlot):</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> print(InSlot) </span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> </span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> #Split the string into parts using the space character </span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> SplitStr = InSlot.split()</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> </span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> OutStr = "" #Just initialise to avoid a reference before assignment error</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> </span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> #Take each component and add a + to the end </span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> for SubStr in SplitStr:</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> OutStr = OutStr + SubStr + "+"</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> </span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> #Just trim the final + off as we don't need it</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: xx-small;"> return OutStr[:-1]</span><br />
<div>
<br /></div>
Geek Dadhttp://www.blogger.com/profile/12159682162763730420noreply@blogger.com3tag:blogger.com,1999:blog-9130450719881994773.post-46620508865888491732017-01-23T20:20:00.002+00:002017-01-23T20:20:34.371+00:00Amazon Alexa Skill with Python and Strava APIIn my <a href="http://pdwhomeautomation.blogspot.co.uk/2017/01/my-first-amazon-alexa-skill.html" target="_blank">last post</a> I described how I'd followed a step-by-step guide to create a Amazon Alexa Skill for my <a href="http://rover.ebay.com/rover/1/710-53481-19255-0/1?icep_ff3=9&pub=5575115886&toolid=10001&campid=5337651934&customid=&icep_uq=amazon+echo&icep_sellerId=&icep_ex_kw=&icep_sortBy=12&icep_catId=&icep_minPrice=&icep_maxPrice=&ipn=psmain&icep_vectorid=229508&kwid=902099&mtid=824&kw=lg" target="_blank">Amazon Echo Dot</a>. This used Node.js and was basically an easy "join-the-dots" guide to creating your first skill and getting it certified.<br />
<br />
Building on this I wanted to build a skill that:<br />
<br />
<ol>
<li>Uses Python - my language of choice.</li>
<li>Calls an API (rather than just responding with pre-canned data).</li>
<li>Teaches me more about how to configure skills to do different things.</li>
</ol>
<br />
Here's the skill in action. I'll then describe how I made it:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<iframe width="320" height="266" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/lOPSqeUL_G0/0.jpg" src="https://www.youtube.com/embed/lOPSqeUL_G0?feature=player_embedded" frameborder="0" allowfullscreen></iframe></div>
<br />
<br />
To start with I used the Amazon Python "Colour Expert" skill which can be found <a href="https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/alexa-skill-tutorial" target="_blank">here</a>. Follow this if it's your first time with an Alexa skill as it will show you how to use the Amazon Developer site and Amazon Web Services Lambda to create a skill using Python.<br />
<br />
My idea was to modify this skill to fetch and read out data from my Strava (exercise logging) account. I've previously blogged on using the Strava API in posts like <a href="http://pdwhomeautomation.blogspot.co.uk/2014/11/raspberry-pi-and-strava-api-1.html" target="_blank">this</a> and <a href="http://pdwhomeautomation.blogspot.co.uk/2014/12/raspberry-pi-and-strava-api-2.html" target="_blank">this</a>.<br />
<br />
To modify the Colour Expert skill I initially did the following on the Amazon Developer site on the "Skill Information" tab:<br />
<br />
<ul>
<li>Name = "Sports Geek Stuff". This is just what you'd see on the Alexa smartphone app if you published the skill.</li>
<li>Invocation name = "sports geek". This is what say to Alexa to specify you're using a particular skill. So you'd start by saying "Alexa, ask sports geek" then subsequent words define what you want the skill to do.</li>
</ul>
<br />
I then added extra configuration on the "Interaction Model" tab to define how I should interact with the skill to get the Strava data.<br />
<br />
The "Intent Schema" basically creates a structure that maps things you say to Alexa to the associated functions that you run in the AWS Lambda Python script (more on this below). I added the following to the bottom of the Intent Schema.<br />
<br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> {</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> "intent": "StravaStatsIntent"</span><br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;"> } </span><br />
<br />
I then defined an utterance (so basically a thing you say) that links to this intent. The utterance was:<br />
<br />
<span style="color: #38761d; font-family: "courier new" , "courier" , monospace; font-size: x-small;">StravaStatsIntent for strava stats</span><br />
<br />
...which basically means, when you say "Alexa, ask sports geek for strava stats" then Alexa calls the associated Python script in AWS Lambda with the parameter "StravaStatsIntent" to define what function to call.<br />
<br />
Apart from ace voice to text translation, there's very little intelligence here. You could configure:<br />
<br />
<span style="color: #38761d; font-family: "courier new", courier, monospace; font-size: x-small;">StravaStatsIntent for a badger's sticker collection</span><br />
<br />
...or even...<br />
<br />
<span style="color: #38761d; font-family: "courier new", courier, monospace; font-size: x-small;">StravaStatsIntent for brexit means brexit</span><br />
<br />
...and these crazy sayings would still result in the StravaStatsIntent being selected.<br />
<br />
You also configure the Alexa skill to map to a single AWS Lambda function which will handle all the intents you configure. So in simple terms a invocation name selects a Alexa skill which is linked to an AWS Lambda function. Then utterances are configured that link to intents, each of which is handled by the Lambda function.<br />
<br />
Here's a simple diagram of how it all hangs together:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhhI5qbFLr1iEh_s-DmKoHMMysRw508wv5MkfcsQjJtBigffDwkcVOxZRdPPeizyUPGjWXjOMe5oGMazmaBLQduWXgRb-PI0pBW6Iwnuq-p_fCmj3NDkV2D9xNygJop_FdA-RPLvNZYdeZX/s1600/arch2.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="305" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhhI5qbFLr1iEh_s-DmKoHMMysRw508wv5MkfcsQjJtBigffDwkcVOxZRdPPeizyUPGjWXjOMe5oGMazmaBLQduWXgRb-PI0pBW6Iwnuq-p_fCmj3NDkV2D9xNygJop_FdA-RPLvNZYdeZX/s640/arch2.PNG" width="640" /></a></div>
<br />
<br />
So next you have to edit the Python Lambda function to handle the intents. I left the colour expert<br />
skill as is and just added code for my Strava intent. There is some other interesting aspects of the Python script that I'll explore later (these are slots and session handling) so I didn't want to remove this.<br />
<br />
To modify the code I went to AWS, logged in, selected Lambda and chose to edit the code inline. This gave me a screen like this that I could use to edit the Python script:<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhByTpT4oDBlwn8q8l2zsETXV6k8ljVa2wBYKOWR4KXdoT9ePmf3A5JTqbzyF-1tQZ4zFvNtdULThybBhkDR8YIoLAT2TfIl4LeUwOJKRkLLFQ5uBmiG4WtuEexdHONUOnDNpvvt3zYv4Gl/s1600/aws1.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhByTpT4oDBlwn8q8l2zsETXV6k8ljVa2wBYKOWR4KXdoT9ePmf3A5JTqbzyF-1tQZ4zFvNtdULThybBhkDR8YIoLAT2TfIl4LeUwOJKRkLLFQ5uBmiG4WtuEexdHONUOnDNpvvt3zYv4Gl/s640/aws1.PNG" width="640" /></a></div>
<br />
To modify the code I firstly added references to the Python urllib2 and json modules as I need to use these, (you can see them in the image above).<br />
<br />
I also added my Strava developer API key and a Unix timestamp to use for the API call as constants.<br />
<br />
I then edited the on_intent function to specify that the StravaStatsIntent would be passed. This is shown in red below.<br />
<br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> # Dispatch to your skill's intent handlers</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> if intent_name == "MyColorIsIntent":</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> return set_color_in_session(intent, session)</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> elif intent_name == "WhatsMyColorIntent":</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> return get_color_from_session(intent, session)</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> elif intent_name == "AMAZON.HelpIntent":</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> return get_welcome_response()</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> elif intent_name == "AMAZON.CancelIntent" or intent_name == "AMAZON.StopIntent":</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> return handle_session_end_request()</span><br />
<span style="font-family: "Courier New", Courier, monospace; font-size: x-small;"><span style="color: red;"> elif intent_name == "StravaStatsIntent":</span></span><br />
<span style="color: red; font-family: Courier New, Courier, monospace; font-size: x-small;"> return handle_strava() </span><span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> </span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> else:</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> raise ValueError("Invalid intent")</span><br />
<br />
I then created the handle_strava() function, all of which is shown below. Yes, I know my code is clunky!<br />
<br />
Key points here are:<br />
<ul>
<li>Making the API call using urllib2 and getting a response</li>
<li>Parsing the JSON and building an output string</li>
<li>Not using reprompt_text which could be used to prompt the user again as to what to say</li>
<li>Setting should_end_session to true as we don't want the session to continue beyond this point</li>
<li>Calling the build_response function to actually build the response to pass back to the Alexa skill</li>
</ul>
<br />
<br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;">#Get us some Strava stats</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;">def handle_strava():</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> """ If we wanted to initialize the session to have some attributes we could</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> add those here</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> """</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> session_attributes = {}</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> card_title = "parkrun"</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> </span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> #Access the Strava API using a URL</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> StravaText = urllib2.urlopen('https://www.strava.com/api/v3/activities?access_token=' + StravaToken + '&per_page=200&after=' + TheUnixTime).read()</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> </span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> #Parse the output to get all the information. Set up some variables</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> SwimCount = 0</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> SwimDistance = 0</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> RunCount = 0</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> RunDistance = 0</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> BikeCount = 0</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> BikeDistance = 0</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> #See how many Stravas there are.Count the word 'name', there's one per record</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> RecCount = StravaText.count('name')</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> #Load the string as a JSON to parse</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> StravaJSON = json.loads(StravaText)</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> #Loop through each one</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> for i in range(0,RecCount):</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> #See what type it was and process accordingly</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> if (StravaJSON[i]['type'] == 'Swim'):</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> SwimCount = SwimCount + 1</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> SwimDistance = SwimDistance + StravaJSON[i]['distance']</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> elif (StravaJSON[i]['type'] == 'Ride'):</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> BikeCount = BikeCount + 1</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> BikeDistance = BikeDistance + StravaJSON[i]['distance']</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> elif (StravaJSON[i]['type'] == 'Run'):</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> RunCount = RunCount + 1</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> RunDistance = RunDistance + StravaJSON[i]['distance']</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> </span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> #Turn distances into km</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> SwimDistance = int(SwimDistance / 1000)</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> BikeDistance = int(BikeDistance / 1000)</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> RunDistance = int(RunDistance / 1000)</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> </span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> #Build the speech output</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> speech_output = 'Swim Count = ' + str(SwimCount) + '. Swim Distance = ' + str(SwimDistance) + " kilometres. "</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> speech_output = speech_output + 'Bike Count = ' + str(BikeCount) + '. Bike Distance = ' + str(BikeDistance) + " kilometres. "</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> speech_output = speech_output + 'Run Count = ' + str(RunCount) + '. Run Distance = ' + str(RunDistance) + " kilometres."</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> </span><br />
<span style="color: #38761d; font-family: "Courier New", Courier, monospace; font-size: x-small;"> # If the user either does not reply to the welcome message or says something</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> # that is not understood, they will be prompted again with this text.</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> # Now we set re-prompt text to None. See notes elsewhere for what this means</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> #reprompt_text = "Please tell me your favorite color by saying, " \</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> # "my favorite color is red."</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> #This could be set to false of you want the session to continue</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> should_end_session = True</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> reprompt_text = None</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"><br /></span>
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> return build_response(session_attributes, build_speechlet_response(</span><br />
<span style="color: #38761d; font-family: Courier New, Courier, monospace; font-size: x-small;"> card_title, speech_output, reprompt_text, should_end_session))</span><br />
<div>
<br /></div>
<br />
You can test if you have a Amazon Echo device or just test using the Alexa Skills Kit test capability.<br />
<br />
<br />Geek Dadhttp://www.blogger.com/profile/12159682162763730420noreply@blogger.com0tag:blogger.com,1999:blog-9130450719881994773.post-10332547930252919932017-01-15T17:02:00.000+00:002017-01-15T17:04:43.093+00:00My First Amazon Alexa SkillRecently I bought an <a href="http://rover.ebay.com/rover/1/710-53481-19255-0/1?icep_ff3=9&pub=5575115886&toolid=10001&campid=5337651934&customid=&icep_uq=amazon+echo&icep_sellerId=&icep_ex_kw=&icep_sortBy=12&icep_catId=&icep_minPrice=&icep_maxPrice=&ipn=psmain&icep_vectorid=229508&kwid=902099&mtid=824&kw=lg" target="_blank">Amazon Echo Dot</a> as my colleagues had been raving about them. Oh, my, what an excellent piece of kit it is. As long as you speak clearly and think about the clarity of the words you use then the Alexa voice recognition system rarely fails.<br />
<br />
There's plenty of reviews about Alexa and the Echo Dot on the interweb so I won't go into general usage here. (Although the <a href="https://turbofuture.com/consumer-electronics/200-Amusing-Amazon-Echo-Easter-Eggs" target="_blank">Easter Eggs</a> are excellent fun). <br />
<br />
As a Geek, my main driver for buying an Echo Dot was to write my own Alexa Skills. I started using <a href="https://developer.amazon.com/blogs/post/Tx3DVGG0K0TPUGQ/New-Alexa-Skills-Kit-Template:-Step-by-Step-Guide-to-Build-a-Fact-Skill" target="_blank">this tutorial</a> and it's so super easy! Usually I'd talk through the tutorial in detail on this blog but it was so easy it's not worth going through the detail of this. <br />
<br />
What I will do is provide an super-simple "architectural" diagram of how it all works. Here it is:<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjnTK3FBPfhiuTf27t5QWbX5DcumcXhc8Af6Q4L3HP6D_r5IzsjibqnNKnr_UJCaO0fbpjJitGJtoodwFJAiuuLjZAPvjXfWwaPM1BjrRfOi_aigaDAUH41-_3weJQKV5fYB6Imfhrbtxlj/s1600/alexa1.PNG" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="224" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjnTK3FBPfhiuTf27t5QWbX5DcumcXhc8Af6Q4L3HP6D_r5IzsjibqnNKnr_UJCaO0fbpjJitGJtoodwFJAiuuLjZAPvjXfWwaPM1BjrRfOi_aigaDAUH41-_3weJQKV5fYB6Imfhrbtxlj/s640/alexa1.PNG" width="640" /></a></div>
<br />
So in simple terms, to create a skill you:<br />
<br />
<ol>
<li>Configure the skill and associated attributes in the Amazon Skills Kit from the <a href="https://developer.amazon.com/" target="_blank">Amazon Developer</a> site. This is generally about the language you'll use to interact with the skill. The site also takes you through all the workflow from defining your Skill to testing it then certifying it.</li>
<li>Define a function in <a href="https://aws.amazon.com/" target="_blank">Amazon Web Services</a> Lambda to actually handle the logic behind your Alexa skill.</li>
</ol>
<br />
(Note you don't have to use AWS Lambda, you can define your own web service and logic to interact with the Alexa Skills Kit. Additionally the function that handles the Alexa logic can make calls out to the internet to gather further information to augment your skill, can write to databases etc).<br />
<br />
The tutorial mentioned above is super easy to follow. The only step I vaguely had trouble with is where it covers setting up a node.js environment but I managed to do this by following the steps super carefully.<br />
<br />
So I developed the skill, tested it, had it certified by Amazon and now it's available on the Amazon Alexa app to be enabled by anyone with an Echo or Echo Dot. Proud times! (I do realise that this was super easy to do so I shouldn't boast too much!).<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgKPkfUsh2Pu5QdoIuCi-8ZrXgJhzKQTnTtsrSYE2gTVAbT-6LPdn7ECCZ-vG0L7xTRb7vVcoR31tXQdjStGsAKCBA_gN6b0BKb20sZXabVubvrwAb2y7OCob8qfM31rqALjFN8omQZc6Ja/s1600/Screenshot_2017-01-12-11-52-43.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="320" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgKPkfUsh2Pu5QdoIuCi-8ZrXgJhzKQTnTtsrSYE2gTVAbT-6LPdn7ECCZ-vG0L7xTRb7vVcoR31tXQdjStGsAKCBA_gN6b0BKb20sZXabVubvrwAb2y7OCob8qfM31rqALjFN8omQZc6Ja/s320/Screenshot_2017-01-12-11-52-43.png" width="180" /></a></div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<div class="separator" style="clear: both; text-align: left;">
Here's a video of it in action:</div>
<div class="separator" style="clear: both; text-align: center;">
<br /></div>
<iframe allowfullscreen="" class="YOUTUBE-iframe-video" data-thumbnail-src="https://i.ytimg.com/vi/J_K9VPyx0Gc/0.jpg" frameborder="0" height="266" src="https://www.youtube.com/embed/J_K9VPyx0Gc?feature=player_embedded" width="320"></iframe><br />
<br />
<br />
<br />Geek Dadhttp://www.blogger.com/profile/12159682162763730420noreply@blogger.com1