for scientific computing
You can edit the code samples from the slides live and run them as you please.
Control-Enter
.Shift-Enter
to run and move to the next cell.x = 1 + 1
10 * x
20
Today's course has two parts:
NumPy
: working with large data gridsSciPy
: common numerical functionsmatplotlib
: in-depth plotting libraryPlus advice, links to resources, exercises, ...
print("Hello, world!")
Hello, world!
import json, random
#Data obtained from http://www.imdb.com/interfaces
with open("data/top_250_imdb.json") as data_file:
films = json.load(data_file)
random.sample(films.items(), 3)
[('Yôjinbô (1961)', 8.2), ('Batman Begins (2005)', 8.2), ('Das Leben der Anderen (2006)', 8.4)]
from statistics import mean
#This mean is just from the top 250!
mean(films.values())
8.2636
max(films.values())
9.2
print([name for name, score in films.items() if score == 9.2])
['The Shawshank Redemption (1994)', 'The Godfather (1972)']
More pros and cons discussed at the SciPy tutorial.
Pandas
dataframes and NumPy
arrays)import pandas #Data from Thomas Bland
df = pandas.read_csv("data/soliton_collision.csv", index_col=0)
df.shape
(450, 1021)
df.head()
0 | 0.98 | 1.96 | 2.94 | 3.92 | 4.9 | 5.88 | 6.86 | 7.84 | 8.82 | ... | 990.78 | 991.76 | 992.74 | 993.72 | 994.7 | 995.68 | 996.66 | 997.64 | 998.62 | 999.6 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
-22.5 | 1.0 | 0.99992 | 0.99991 | 0.99998 | 0.99944 | 0.99935 | 0.99995 | 0.99853 | 1.00030 | 1.0019 | ... | 0.99888 | 1.00010 | 0.99949 | 0.99871 | 0.99616 | 0.99866 | 0.99587 | 0.99769 | 0.99823 | 1.0014 |
-22.4 | 1.0 | 0.99994 | 0.99992 | 1.00010 | 0.99947 | 0.99951 | 1.00000 | 0.99873 | 1.00030 | 1.0018 | ... | 0.99885 | 1.00000 | 0.99935 | 0.99860 | 0.99643 | 0.99857 | 0.99613 | 0.99769 | 0.99813 | 1.0015 |
-22.3 | 1.0 | 0.99995 | 0.99993 | 0.99976 | 1.00000 | 0.99972 | 0.99986 | 0.99892 | 0.99978 | 1.0019 | ... | 0.99873 | 0.99983 | 0.99903 | 0.99840 | 0.99670 | 0.99842 | 0.99643 | 0.99770 | 0.99792 | 1.0016 |
-22.2 | 1.0 | 0.99996 | 0.99994 | 0.99969 | 1.00010 | 1.00010 | 1.00000 | 0.99941 | 0.99972 | 1.0015 | ... | 0.99851 | 0.99944 | 0.99880 | 0.99835 | 0.99725 | 0.99816 | 0.99681 | 0.99766 | 0.99771 | 1.0018 |
-22.1 | 1.0 | 0.99997 | 0.99995 | 0.99995 | 1.00040 | 1.00030 | 0.99980 | 0.99974 | 0.99997 | 1.0010 | ... | 0.99824 | 0.99916 | 0.99843 | 0.99825 | 0.99759 | 0.99808 | 0.99702 | 0.99763 | 0.99771 | 1.0018 |
5 rows × 1021 columns
matplotlib
and MayaVi
)subset = df[-7:7]
import matplotlib.pyplot as plt
plt.imshow(subset, #Like Matlab's pcolor()
aspect='auto',
extent=(0, 1000, -7, 7))
colorbar = plt.colorbar()
colorbar.ax.set_ylabel('Density $|\psi|^2$', labelpad=20, rotation=270)
plt.xlabel('time $t$')
plt.ylabel('position $z$')
plt.show()
Won't always have this notebook interface!
Can try an IDE e.g. Spyder
1 + 2
3
300 - 456
-156
-2 ** 1000 # No problems with sign or under/overflow
-10715086071862673209484250490600018105614048117055336074437503883703510511249361224931983788156958581275946729175531468251871452856923140435984577574698574803934567774824230985421074605062371141877954182153046474983581941267398767559165543946077062914571196477686542167660429831652624386837205668069376
type(-2 ** 1000)
int
1 + 1.5 # Mix int and float: result is float
2.5
type(12 + 24.0) #Can check types explicitly
float
23 - 7.0
16.0
2 * 4
8
3 / 2 # division always returns a float in Python 3
1.5
3 // 2 # double-slashes force integer division
1
2 ** 3.0
8.0
2 ^ 6 #Bitwise or -- not very useful for scientists
4
(1 + 2) * (3 + 4) #Brackets work as normal
21
3 - 2*4 #Order of operations (BODMAS) as normal
-5
27 % 5 #Modulo (remainder) operation
2
abs(-2) #Modulus (absolute value) function
2
math.isclose
)x = 0.1 + 0.2
y = 0.15 + 0.15
print("%.20f\n%.20f" % (x, y))
from math import isclose
isclose(x, y)
0.30000000000000004441 0.29999999999999998890
True
complex
type¶j
for the imaginary unit $i$.j
.1j * 1j
(-1+0j)
z = 2 - 4j
z + z.conjugate() # Twice the real part
(4+0j)
cmath
functions when working with complex numbers.import cmath
cmath.sin(0.1 + 2j)
(0.37559284993485376+3.6087412126897433j)
abs(cmath.exp(2j))
1.0
What are the types and values of the following expressions? Try to work it out by hand; then check in the notebook.
23 + 2 * 17 - 9
23 + 2 * (17 - 9.0)
5 * 6 / 7
5 * 6 // 7
5 * 6.0 // 7
2.0 ** (3 + 7 % 3) // 2
2 ** (3 + 7 % 3) / 2
4 ** 0.5
-4 ** 0.5
(1 + 1/1000) ** 1000
48
39.0
30/7 == 4.28571...6
30 // 7 == 4
30.0 // 7 == 4.0
8.0
8.0
2.0
-2.0
2.71692...
$\approx e$Variables are names which refer to values.
x = 10
2 * x + 4
24
#Prefer descriptive names over shorthand
import math
planck = 6.63e-36
red_planck = planck / (2 * math.pi)
red_planck
1.0551972726992662e-36
name = 'Dr. John Smith' #not just numbers: more data types later
len(name)
14
thing1 = 3.142 #numbers okay in variable names
thing2 = 1.618
3rdthing = 2.718 #except at the start
File "<ipython-input-38-e4d50dee3627>", line 1 3rdthing = 2.718 #except at the start ^ SyntaxError: invalid syntax
Some keywords are forbidden.
del = 'boy'
File "<ipython-input-41-6e337587edb8>", line 1 del = 'boy' ^ SyntaxError: invalid syntax
To compare variables and/or values, use two equals signs ==
. More on this later.
t = 2
t + t = 4
File "<ipython-input-40-c6ff51bde1a1>", line 1 t + t = 4 ^ SyntaxError: can't assign to operator
t + t == 4
True
x = 1
y = x
x = x * 5
What's $y$ equal to: $1$ or $5$?
y
1
When we say y = x
, we mean
y
refer to whatever x
refers toand not
y
refer to x
If in doubt: try experimenting!
def discriminant(a, b, c):
print("a =", a, "b =", b, "c =", c)
return b ** 2 - 4 * a * c
def
keyword (define)return
expressiondiscriminant(2, 3, 4) #Give arguments values by position...
a = 2 b = 3 c = 4
-23
discriminant(b=3, c=4, a=2) #...or explicitly by name
a = 2 b = 3 c = 4
-23
Python will complain if you don't give a function the right arguments.
discriminant()
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-48-dc883d99b76f> in <module>() ----> 1 discriminant() TypeError: discriminant() missing 3 required positional arguments: 'a', 'b', and 'c'
discriminant(0, 0)
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-49-05674ee3aefb> in <module>() ----> 1 discriminant(0, 0) TypeError: discriminant() missing 1 required positional argument: 'c'
discriminant(a=1, a=2, a=3)
File "<ipython-input-50-cf03c5c67bda>", line 1 discriminant(a=1, a=2, a=3) ^ SyntaxError: keyword argument repeated
Arguments can be made optional by giving them default values.
def greet(greeting='Hello', name='stranger'):
print(greeting, 'to you,', name)
greet()
Hello to you, stranger
greet('David')
David to you, stranger
greet(name='David')
Hello to you, David
Can return more than one value at once:
def consecutive_squares(n):
return n**2, (n + 1)**2
consecutive_squares(5)
(25, 36)
The function returns a tuple
(more on these later).
Can unpack to get at the individual values
a, b = consecutive_squares(10)
a
100
b
121
a = 3
def double(a):
a = 2 * a
return a
double(6)
12
Function arguments and variables defined in a function are local to the function body.
If there's a name conflict, stuff outside is unaffected.
a
3
See the Python tutorial for more tips, tricks and examples---including functions that take a variable number of arguments.
Write a function implements the quadratic formula.
Reminder: the quadratic formula is $$x = \frac {-b \pm \sqrt{b^2 - 4ac}} {2a}$$
math.sqrt
for computing square roots. Don't forget to import
!Let's do a few tests.
print( quadratic_roots(1, 2, -8) )
#assert statements will error if the condition is False.
assert quadratic_roots(1, 2, -8) == (-2, 4)
assert quadratic_roots(2, -40, 400) == (10, 10)
for i in range(5):
print("Hello!")
Hello! Hello! Hello! Hello! Hello!
Something of length $N$ uses indices from $0$ to $N-1$ inclusive.
for i in range(5):
print("Here's a number:")
print(i)
Here's a number: 0 Here's a number: 1 Here's a number: 2 Here's a number: 3 Here's a number: 4
for i in range(5, 10):
print(i)
5 6 7 8 9
for i in range(10, 20, 2):
print(i)
10 12 14 16 18
Python assumes that start ≤ stop.
for thing in range(50, 40): #can use any loop variable
print(thing)
If you want a descending loop you need a negative step.
for thing in range(50, 40, -3):
print(thing)
50 47 44 41
Use a loop to compute $$5^2 + 10^2 + 15^2 + 20^2 + \dotsb + 200^2$$
#Again here's a template for you
total = 0
for ... in ...:
total = total + ...
total
#Here's the answer you should have got:
assert total == 553500
We'll see later that we can loop over all sorts of objects---not just range
s.
for character in "David Matthew Robertson":
print(character, end=".")
D.a.v.i.d. .M.a.t.t.h.e.w. .R.o.b.e.r.t.s.o.n.
This makes looping a really powerful tool in Python. It enables
enumerate
functionJust like other languages, there are while
loops and break
and continue
statements which are a bit less intuitive.
There's too much to go over here---but there are links in the notebook if you're curious.
A very important tool in the programmer's toolkit is the ability to do different things in different circumstances.
Enter the if
statement:
i = 10
if i % 2 == 0:
print(i, "is even")
10 is even
True
or False
<
, <=
, ==
, !=
, >=
, >
to make booleans1 < 2 #less than
True
2 <= 0.2 #less than or equal
False
3 == 3.0 #equal
True
"cat" != "dog" #not equal
True
x = 10
1 < x < 15 #Mathematical notation for "(1 < x) and (x < 15)"
True
Let's take our previous if statement and put it in a loop.
Whenever we start a new block (line ending in a colon), we have to indent an extra four spaces.
for i in range(5):
if i % 2 == 0:
print(i, "is even")
0 is even 2 is even 4 is even
We can handle the False
case with an else
statement.
for i in range(5):
if i % 2 == 0:
print(i, "is even")
else:
print(i, "is odd")
0 is even 1 is odd 2 is even 3 is odd 4 is even
For finer control, use an if... elif... else...
chain.
Here elif
is short for "else if".
import datetime
now = datetime.datetime.now()
print("The time is", now, "and the hour is:", now.hour)
if 6 <= now.hour < 12:
print("Good morning!")
elif now.hour < 18:
print("Good afternoon!")
elif now.hour < 20:
print("Good evening!")
else:
print("Good night!")
The time is 2017-04-11 12:48:23.167619 and the hour is: 12 Good afternoon!
else
is optional and always comes last.if
before any elif
s.elif
s as you like.The sign or signum function is defined by $$\operatorname{sign}(x) = \begin{cases} \phantom{-}1 & \text{if $x>0$} \\ \phantom{-}0 & \text{if $x=0$} \\ -1 & \text{if $x<0$} \end{cases}$$
Implement this as a Python function.
#And some tests:
assert sign(10) == 1
assert sign(0) == 0
assert sign(-23.4) == -1
and
, or
, and not
.True and False
False
True or False
True
not False
True
not False and False #careful with order of operations
False
not (False and False)
True
supercal = "Supercalifragilisticexpialidocious"
starwars = 'No, I am your father' # spaces okay
greeting = "こんにちは (Konnichiwa)" # non-Latin characters okay
\n
to stand for a newline\'
or \"
for literal quotes \\
for a literal backslashprint("A short 'quote'\n a double quote char: \"\n and newlines!")
A short 'quote' a double quote char: " and newlines!
'2' == 2 #different types!
False
type('2'), type(2)
(str, int)
'True' == True
False
type('True'), type(True)
(str, bool)
A list of handy funtions for working with strings. Full reference online.
vowels = "aeiou"
vowels.upper()
'AEIOU'
vowels.lower() #already lowercase
'aeiou'
vowels.capitalize()
'Aeiou'
len(supercal) #length function
34
supercal.count("a")
3
Silly example: a function which processes a yes/no prompt (y/n)
def handle_response(response):
if response.startswith("y"):
return "positive response"
elif response.startswith("n"):
return "negative response"
else:
return "unclear response"
handle_response("yes")
'positive response'
handle_response("no way man that's unreasonable")
'negative response'
What happens when we call with these arguments? Guess, then check in the notebook.
handle_response()
handle_response("")
handle_response("YES")
handle_response(" yes ")
handle_response()
TypeError
: missing argumenthandle_response("")
""
doesn't start with anything!handle_response("YES")
'Y' == 'y'
False
handle_response(" yes ")
Often useful to normalise strings to a sensible form, especially if they come from user input.
response = " YeS "
response = response.lower()
print( repr(response) ) # explicitly representation with repr()
response = response.strip() # remove whitespace from start and end
print( repr(response) )
' yes ' 'yes'
Also handy: str.replace
:
x = "The news media reported today that no news is in fact good news"
x.replace("news", "FAKE NEWS!!")
'The FAKE NEWS!! media reported today that no FAKE NEWS!! is in fact good FAKE NEWS!!'
Remember that indexing works from $0$ to $N - 1$:
supercal[0]
'S'
supercal[5]
'c'
supercal[0:5] #like range, slicing excludes upper limit
'Super'
supercal[-1] #Last char
's'
supercal[:5] + "..." + supercal[-4:] #first five, then last 4
'Super...ious'
name = "David"
"Good morning, " + name + "."
'Good morning, David.'
*
as shorthand for repitition.'thank you ' * 10
'thank you thank you thank you thank you thank you thank you thank you thank you thank you thank you '
Even more complicated string handling available:
Awkward way:
example = "demo"
for i in range(len(example)):
print(example[i])
d e m o
Slick way:
for character in "demo":
print(character)
d e m o
Write a function to count the number of vowels in a string. Assume that we're just working with the Roman alphabet---so don't worry about variants like ë, è, é, and ê.
For bonus points, try using a loop to write this function.
#Here's a space to write your function
#and some tests to run
assert your_function("Hello") == 2
assert your_function(" xyz HEllO") == 2
assert your_function("Hello, sailor") == 5
greek_letters = ["alpha", "beta", "gamma", "delta"]
greek_letters[1] #Index just like strings: 0 to N-1.
'beta'
greek_letters[1] = "BETA (β)"
greek_letters
['alpha', 'BETA (β)', 'gamma', 'delta']
things = ["uno", "dos", 3, supercal, 2.718]
len(things)
5
names_by_parts = [ ["David", "Robertson"], ["Cetin", "Can", "Evirgen"] ]
print( names_by_parts[0] )
print( names_by_parts[0][1] )
['David', 'Robertson'] Robertson
len(names_by_parts)
2
greek_letters[4]
--------------------------------------------------------------------------- IndexError Traceback (most recent call last) <ipython-input-117-85a9bd08274d> in <module>() ----> 1 greek_letters[4] IndexError: list index out of range
greek_letters[4] = 'EPSILON (ε)'
--------------------------------------------------------------------------- IndexError Traceback (most recent call last) <ipython-input-118-233d7087fd08> in <module>() ----> 1 greek_letters[4] = 'EPSILON (ε)' IndexError: list assignment index out of range
greek_letters.append("EPSILON (ε)")
greek_letters
['alpha', 'BETA (β)', 'gamma', 'delta', 'EPSILON (ε)']
empty_list = []
print(empty_list, len(empty_list))
[] 0
numbers = [5, 2, 64, 41, 27, -2, 11, 32]
numbers.sort() #modifies list in place
numbers
[-2, 2, 5, 11, 27, 32, 41, 64]
["ab", 1].sort() # Can't compare text with numbers
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-123-c281d25ec96a> in <module>() ----> 1 ["ab", 1].sort() # Can't compare text with numbers TypeError: '<' not supported between instances of 'int' and 'str'
x = list(range(10, 20))
x
[10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
del x[2] #Delete the entry with index 2 (third entry)
x
[10, 11, 13, 14, 15, 16, 17, 18, 19]
print("POP:", x.pop(), x)
POP: 19 [10, 11, 13, 14, 15, 16, 17, 18]
x.reverse() #modifies in place
x
[18, 17, 16, 15, 14, 13, 11, 10]
x.insert(4, "surprise")
x
[18, 17, 16, 15, 'surprise', 14, 13, 11, 10]
NB: It's quick to extend lists at the end, but inserting or delete near the start is slower. If your list is HUGE then this can become a problem.
See also the Python wiki or these course notes.
Looping over lists is just like strings.
Warning: don't modify list structure when looping! (Modifying list values is fine)
colours = ["red", "orange", "yellow", "green", "blue", "indigo", "violet"]
for colour in colours:
print(colour, "has", len(colour), "letters" )
red has 3 letters orange has 6 letters yellow has 6 letters green has 5 letters blue has 4 letters indigo has 6 letters violet has 6 letters
for colour in colours:
colours.pop()
colours
['red', 'orange', 'yellow']
for i, colour in enumerate(colours): #avoids range(len(colours))
colours[i] = colour.upper()
colours
['RED', 'ORANGE', 'YELLOW']
with open('data/en-GB-words.txt', 'rt') as f:
words = [line.strip() for line in f]
print(len(words), "words. Number 2001 is", words[2000])
99156 words. Number 2001 is Booth
N = len(words)
print(words[0], words[N//2], words[-1], sep=", ")
A, harks, études
count = 0
for word in words:
if 'e' in word:
count = count + 1
print(count, 100 * count / len(words))
63152 63.689539715196254
two_letter_words = []
for word in words:
if len(word) == 2:
two_letter_words.append(word)
print(two_letter_words)
['Ac', 'Ag', 'Al', 'Am', 'Ar', 'As', 'At', 'Au', 'Av', 'Ba', 'Be', 'Bi', 'Bk', 'Br', 'Ca', 'Cd', 'Cf', 'Ci', 'Cl', 'Cm', 'Co', 'Cr', 'Cs', 'Cu', 'Di', 'Dr', 'Ed', 'Er', 'Es', 'Eu', 'Fe', 'Fm', 'Fr', 'GE', 'Ga', 'Gd', 'Ge', 'He', 'Hf', 'Hg', 'Ho', 'Hz', 'In', 'Io', 'Ir', 'It', 'Jo', 'Jr', 'Kr', 'La', 'Le', 'Li', 'Ln', 'Lr', 'Lt', 'Lu', 'Mb', 'Md', 'Mg', 'Mn', 'Mo', 'Mr', 'Ms', 'Mt', 'Na', 'Nb', 'Nd', 'Ne', 'Ni', 'Np', 'OK', 'Ob', 'Os', 'Oz', 'Pa', 'Pb', 'Pd', 'Pl', 'Pm', 'Po', 'Pt', 'Pu', 'Ra', 'Rb', 'Rd', 'Re', 'Rh', 'Rn', 'Ru', 'Rx', 'Sb', 'Sc', 'Se', 'Si', 'Sm', 'Sn', 'Sq', 'Sr', 'St', 'Ta', 'Tb', 'Tc', 'Th', 'Ti', 'Tl', 'Tm', 'Ty', 'Ur', 'Va', 'Wm', 'Wu', 'Xe', 'Yb', 'Zn', 'Zr', 'ad', 'ah', 'am', 'an', 'as', 'at', 'ay', 'be', 'by', 'cs', 'dB', 'do', 'eh', 'em', 'es', 'ex', 'fa', 'go', 'gs', 'ha', 'he', 'hi', 'ho', 'id', 'if', 'in', 'is', 'it', 'kW', 'kc', 'ks', 'la', 'lo', 'ls', 'ma', 'me', 'mi', 'ms', 'mu', 'my', 'no', 'nu', 'of', 'oh', 'on', 'or', 'ow', 'ox', 'pH', 'pa', 'pi', 're', 'rs', 'sh', 'so', 'ti', 'to', 'ts', 'uh', 'um', 'up', 'us', 'vs', 'we', 'ye', 'yo']
coordinate = (1, 2, 3)
coordinate
(1, 2, 3)
coordinate[0]
1
coordinate[0] = 10
--------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-138-9acc16226b5e> in <module>() ----> 1 coordinate[0] = 10 TypeError: 'tuple' object does not support item assignment
x, y, z = coordinate #tuple unpacking
print(x, y, z, x + y + z)
1 2 3 6
In fact, when you say return a, b
from a function, what gets returned is the tuple (a, b)
!
key -> value
david = dict(
surname = "Robertson",
given_names = ["David", "Matthew"],
age = 24,
dob = "26/06/1992",
height = 190
)
david
{'age': 24, 'dob': '26/06/1992', 'given_names': ['David', 'Matthew'], 'height': 190, 'surname': 'Robertson'}
david['age'] = "very very very very very very old"
david['age']
'very very very very very very old'
for key in david:
print(key, end=", ")
surname, given_names, age, dob, height,
for value in david.values():
print(value, end=", ")
Robertson, ['David', 'Matthew'], very very very very very very old, 26/06/1992, 190,
for key, value in david.items():
print(key, "->", value)
surname -> Robertson given_names -> ['David', 'Matthew'] age -> very very very very very very old dob -> 26/06/1992 height -> 190
len(david)
5
david['weight']
--------------------------------------------------------------------------- KeyError Traceback (most recent call last) <ipython-input-146-01a69e534c10> in <module>() ----> 1 david['weight'] KeyError: 'weight'
in
:'surname' in david
True
dict.get()
or exception handling when keys might be missing.The following data file contains the periodic table as a dictionary. We're going to load it into a list, and each entry of that list will be a dictionary.
import os
import json
with open("data/PeriodicTable.json", "rt") as f:
table = json.loads(f.read())['elements']
table[0]
{'appearance': 'colorless gas', 'atomic_mass': 1.008, 'boil': 20.271, 'category': 'diatomic nonmetal', 'color': None, 'density': 0.08988, 'discovered_by': 'Henry Cavendish', 'melt': 13.99, 'molar_heat': 28.836, 'name': 'Hydrogen', 'named_by': 'Antoine Lavoisier', 'number': 1, 'period': 1, 'phase': 'Gas', 'shells': [1], 'source': 'https://en.wikipedia.org/wiki/Hydrogen', 'spectral_img': 'https://en.wikipedia.org/wiki/File:Hydrogen_Spectra.jpg', 'summary': 'Hydrogen is a chemical element with chemical symbol H and atomic number 1. With an atomic weight of 1.00794 u, hydrogen is the lightest element on the periodic table. Its monatomic form (H) is the most abundant chemical substance in the Universe, constituting roughly 75% of all baryonic mass.', 'symbol': 'H', 'xpos': 1, 'ypos': 1}
Your challenges:
D
is the dictionary, D['H'] == 'Hydrogen'
.max_density = 0
max_density_name = ""
for element in table:
if element['density'] != None and element['density'] > max_density:
max_density = element['density']
max_density_name = element['name']
max_density, max_density_name
(40.7, 'Hassium')
shorthand = {}
for element in table:
symbol = element['symbol']
name = element['name']
shorthand[symbol] = name
shorthand
{'Ac': 'Actinium', 'Ag': 'Silver', 'Al': 'Aluminium', 'Am': 'Americium', 'Ar': 'Argon', 'As': 'Arsenic', 'At': 'Astatine', 'Au': 'Gold', 'B': 'Boron', 'Ba': 'Barium', 'Be': 'Beryllium', 'Bh': 'Bohrium', 'Bi': 'Bismuth', 'Bk': 'Berkelium', 'Br': 'Bromine', 'C': 'Carbon', 'Ca': 'Calcium', 'Cd': 'Cadmium', 'Ce': 'Cerium', 'Cf': 'Californium', 'Cl': 'Chlorine', 'Cm': 'Curium', 'Cn': 'Copernicium', 'Co': 'Cobalt', 'Cr': 'Chromium', 'Cs': 'Cesium', 'Cu': 'Copper', 'Db': 'Dubnium', 'Ds': 'Darmstadtium', 'Dy': 'Dysprosium', 'Er': 'Erbium', 'Es': 'Einsteinium', 'Eu': 'Europium', 'F': 'Fluorine', 'Fe': 'Iron', 'Fl': 'Flerovium', 'Fm': 'Fermium', 'Fr': 'Francium', 'Ga': 'Gallium', 'Gd': 'Gadolinium', 'Ge': 'Germanium', 'H': 'Hydrogen', 'He': 'Helium', 'Hf': 'Hafnium', 'Hg': 'Mercury (element)', 'Ho': 'Holmium', 'Hs': 'Hassium', 'I': 'Iodine', 'In': 'Indium', 'Ir': 'Iridium', 'K': 'Potassium', 'Kr': 'Krypton', 'La': 'Lanthanum', 'Li': 'Lithium', 'Lr': 'Lawrencium', 'Lu': 'Lutetium', 'Lv': 'Livermorium', 'Mc': 'Moscovium', 'Md': 'Mendelevium', 'Mg': 'Magnesium', 'Mn': 'Manganese', 'Mo': 'Molybdenum', 'Mt': 'Meitnerium', 'N': 'Nitrogen', 'Na': 'Sodium', 'Nb': 'Niobium', 'Nd': 'Neodymium', 'Ne': 'Neon', 'Nh': 'Nihonium', 'Ni': 'Nickel', 'No': 'Nobelium', 'Np': 'Neptunium', 'O': 'Oxygen', 'Og': 'Oganesson', 'Os': 'Osmium', 'P': 'Phosphorus', 'Pa': 'Protactinium', 'Pb': 'Lead', 'Pd': 'Palladium', 'Pm': 'Promethium', 'Po': 'Polonium', 'Pr': 'Praseodymium', 'Pt': 'Platinum', 'Pu': 'Plutonium', 'Ra': 'Radium', 'Rb': 'Rubidium', 'Re': 'Rhenium', 'Rf': 'Rutherfordium', 'Rg': 'Roentgenium', 'Rh': 'Rhodium', 'Rn': 'Radon', 'Ru': 'Ruthenium', 'S': 'Sulfur', 'Sb': 'Antimony', 'Sc': 'Scandium', 'Se': 'Selenium', 'Sg': 'Seaborgium', 'Si': 'Silicon', 'Sm': 'Samarium', 'Sn': 'Tin', 'Sr': 'Strontium', 'Ta': 'Tantalum', 'Tb': 'Terbium', 'Tc': 'Technetium', 'Te': 'Tellurium', 'Th': 'Thorium', 'Ti': 'Titanium', 'Tl': 'Thallium', 'Tm': 'Thulium', 'Ts': 'Tennessine', 'U': 'Uranium', 'V': 'Vanadium', 'W': 'Tungsten', 'Xe': 'Xenon', 'Y': 'Yttrium', 'Yb': 'Ytterbium', 'Zn': 'Zinc', 'Zr': 'Zirconium'}
symbols = list(shorthand)
symbols.sort()
symbols[0], symbols[-1]
('Ac', 'Zr')
names = list(shorthand.values())
names.sort()
names[0], names[-1]
('Actinium', 'Zirconium')
oddballs = {}
for symbol, name in shorthand.items():
if name[0] != symbol[0]:
oddballs[symbol] = name
oddballs
{'Ag': 'Silver', 'Au': 'Gold', 'Fe': 'Iron', 'Hg': 'Mercury (element)', 'K': 'Potassium', 'Na': 'Sodium', 'Pb': 'Lead', 'Sb': 'Antimony', 'Sn': 'Tin', 'W': 'Tungsten'}