diff --git a/.gitignore b/.gitignore
index 85ce87d..f21aa6b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,5 +3,14 @@ index_files/
build/
.DS_Store
+.ipynb_checkpoints
+
# IDE files
.idea/
+
+# Files generated by Python snippets
+example.txt
+out.csv
+
+# Vscode files
+.vscode/
diff --git a/img/github-mark-white.png b/img/github-mark-white.png
new file mode 100644
index 0000000..50b8175
Binary files /dev/null and b/img/github-mark-white.png differ
diff --git a/img/github-mark-white.svg b/img/github-mark-white.svg
new file mode 100644
index 0000000..d5e6491
--- /dev/null
+++ b/img/github-mark-white.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/img/github-mark.png b/img/github-mark.png
new file mode 100644
index 0000000..6cb3b70
Binary files /dev/null and b/img/github-mark.png differ
diff --git a/img/github-mark.svg b/img/github-mark.svg
new file mode 100644
index 0000000..37fa923
--- /dev/null
+++ b/img/github-mark.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/img/matplotlib_logo.svg b/img/matplotlib_logo.svg
new file mode 100644
index 0000000..3b133c9
--- /dev/null
+++ b/img/matplotlib_logo.svg
@@ -0,0 +1 @@
+
diff --git a/img/miniforge_window_add_to_path.png b/img/miniforge_window_add_to_path.png
new file mode 100644
index 0000000..5e0969c
Binary files /dev/null and b/img/miniforge_window_add_to_path.png differ
diff --git a/img/numpylogoicon.svg b/img/numpylogoicon.svg
new file mode 100644
index 0000000..262f030
--- /dev/null
+++ b/img/numpylogoicon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/img/pandas_logo.svg b/img/pandas_logo.svg
new file mode 100644
index 0000000..e51ebaf
--- /dev/null
+++ b/img/pandas_logo.svg
@@ -0,0 +1 @@
+
diff --git a/img/python-ecosystem.png b/img/python-ecosystem.png
new file mode 100644
index 0000000..e625d40
Binary files /dev/null and b/img/python-ecosystem.png differ
diff --git a/img/python_environment.png b/img/python_environment.png
new file mode 100644
index 0000000..a8e8fd6
Binary files /dev/null and b/img/python_environment.png differ
diff --git a/img/pytorch_logo.svg b/img/pytorch_logo.svg
new file mode 100644
index 0000000..8461b14
--- /dev/null
+++ b/img/pytorch_logo.svg
@@ -0,0 +1 @@
+
diff --git a/img/scikit-image_logo.png b/img/scikit-image_logo.png
new file mode 100644
index 0000000..6f72eb8
Binary files /dev/null and b/img/scikit-image_logo.png differ
diff --git a/img/scipy_logo.png b/img/scipy_logo.png
new file mode 100644
index 0000000..34ecf59
Binary files /dev/null and b/img/scipy_logo.png differ
diff --git a/index.qmd b/index.qmd
index 43919ff..bf9dc70 100644
--- a/index.qmd
+++ b/index.qmd
@@ -1,14 +1,13 @@
---
-title: A template presentation
-subtitle: so much fun
-author: SWC Neuroinformatics Unit
+title: Introduction to Python
+author: Igor Tatarnikov, Laura Porta
execute:
enabled: true
format:
revealjs:
- theme: [default, niu-dark.scss]
- logo: img/logo_niu_dark.png
- footer: "Edit this footer | 2023-07-26"
+ theme: [default, niu-light.scss]
+ logo: img/logo_niu_light.png
+ footer: "Sainsbury Wellcome Centre | 2025-09-30"
slide-number: c
menu:
numbers: true
@@ -17,6 +16,7 @@ format:
preview-links: false
view-distance: 10
mobile-view-distance: 10
+ from: "markdown+emoji"
auto-animate: true
auto-play-media: true
code-overflow: wrap
@@ -26,9 +26,9 @@ format:
fontFamily: arial
curve: linear
html:
- theme: [default, niu-dark.scss]
- logo: img/logo_niu_dark.png
- date: "2023-07-05"
+ theme: [default, niu-light.scss]
+ logo: img/logo_niu_light.png
+ date: "2025-09-30"
toc: true
code-overflow: scroll
highlight-style: atom-one
@@ -39,73 +39,208 @@ format:
margin-left: 0
embed-resources: true
page-layout: full
-my-custom-stuff:
- my-reuseable-variable: "I can use this wherever I want in the markdown, and change it in only once place :)"
---
-## Contents
+## Schedule {.smaller}
+
+::: {.incremental}
+* Aims
+* Why learn Python?
+* Basics
+ + Variables
+ + Data types
+ + Loops
+ + Conditional statements
+ + List comprehensions
+* How to work with Python? (using IDEs, environments, etc...)
+* Writing your first Python script
+* Loading and saving data
+:::
-Some example slides - [also look at example RevealJS slides in the Quarto docs](https://quarto.org/docs/presentations/revealjs/)
+## Schedule {.smaller}
+::: {.incremental}
+* Recap and Q&A
+* Using third party libraries from pip and conda
+* Functions and methods
+* Classes and objects
+* Errors and exceptions
+* Organising your code
+* Documenting your code
+:::
-* Non-executable and executable code-blocks
-* bullet points with highlighting
-* two-column slides
-* how to include a slide from a separate MD file
-* preview and link to a webpage
+## Aims
+::: {.incremental}
+* Introduce Python
+* Introduce some basic programming concepts
+* Show you *one* (our) way of doing things
+ + Not necessarily the best or only way
+* Give you the confidence to start doing it yourself
+* Prepare you for other courses
+:::
-## Just a code block, nothing gets executed...
+::: {.fragment}
+Please ask any **questions at any time**!
+:::
-... but there is some fancy highlighting
+# Why learn Python?
-```{.python code-line-numbers="1|3|4-9"}
-from pathlib import Path
+## Why learn Python? {.smaller}
+::: {.fragment}
+Popularity
-home_path = Path.home()
-if home_path.exists():
- data_path = home_path / "data"
-else:
- pass
- # raise some error maybe?
-```
+- Python is the most popular programming language for data science[$^1$](https://aimresearch.co/product/data-science-skills-study-2023/)
+:::
-## A code block that's actually executed at render-time
+::: {.fragment}
+Free and open source
-```{python}
-#| echo: true
-#| code-fold: true
+- Unlike MATLAB, IGOR, etc...
+- Anyone can use your code
+ + 9.7M open Python repositories[$^2$](As 2024-04-06, github.com/oprogramador/github-languages) on {width=1em}
+:::
-from pathlib import Path
+::: {.fragment}
+Versatile
+Not just for data analysis / machine learning
-print("Hello world")
-```
+- Visualisation
+- Web development
+- Data acquisition
+:::
-## You can execute code without showing that you have by using #|echo: false
-```{python}
-#| echo: false
+## Why learn Python? {.smaller}
+Packages for everything!
-from pathlib import Path
+::: {.fragment}
+{width=2em style="margin-bottom: 0px; vertical-align: bottom;"} Numpy: arrays
+:::
-print("Hello world")
-```
+::: {.fragment}
+{width=2em style="margin-bottom: 0px; vertical-align: bottom;"} Pandas: dataframes
+:::
+
+::: {.fragment}
+{width=2em style="margin-bottom: 0px; vertical-align: bottom;"} SciPy: scientific computing
+:::
+
+::: {.fragment}
+{width=2em style="margin-bottom: 0px; vertical-align: bottom;"} Scikit-image: image analysis
+:::
+
+::: {.fragment}
+{width=2em style="margin-bottom: 0px; vertical-align: bottom;"} PyTorch: machine learning
+:::
+
+::: {.fragment}
+{width=2em style="margin-bottom: 0px; vertical-align: bottom;"} Matplotlib: plotting
+:::
+
+::: {.fragment}
+*People have spent lots of time optimising these packages! Don't reinvent the wheel!*
+
+:::
+
+# Basics of Python
+
+{{< include slides/basics.qmd >}}
+
+# Writing your first Python script
+
+{{< include slides/first_script.qmd >}}
+
+# Loading and saving data
+
+{{< include slides/loading_and_saving.qmd >}}
+
+# Functions
+
+{{< include slides/functions.qmd >}}
+
+# Classes and objects
+
+{{< include slides/classes_and_objects.qmd >}}
+
+# Errors and exceptions
+
+{{< include slides/errors_and_exceptions.qmd >}}
-{{< include slides/extra_slide.qmd >}}
+# How to work with Python?
-## An example image
+{{< include slides/working_with_python.qmd >}}
-Include an image:
+# Virtual environments
-{width=900 fig-align=center}
+{{< include slides/virtual_environments.qmd >}}
+# Modules and packages
-## Link and a preview a webpage:
+{{< include slides/modules_and_packages.qmd >}}
-::: {style="text-align: center; margin-top: 1em"}
-[interoperable Python-based tools for computational neuroanatomy](https://brainglobe.info/index.html){preview-link="true" style="text-align: center"}
+# Organising your code
+
+{{< include slides/structuring_your_code.qmd >}}
+
+# Documenting your code
+
+{{< include slides/documenting_your_code.qmd >}}
+
+# Next steps
+## Next steps {.smaller .incremental}
+* We've covered some of the basics
+* Next steps -- Practice!:
+ + Mess around with code
+ + Solve problems
+ + Google stuff
+ + Contact
+ + Additional courses
+:::
+
+## Further resources {.smaller .incremental}
+* [CS50](https://pll.harvard.edu/course/cs50-introduction-computer-science)
+* UCL ARC courses
+ + [An introduction to programming for research using Python](https://rits.github-pages.ucl.ac.uk/doctoral-programming-intro/)
+ + [Research software engineering with Python](https://github-pages.ucl.ac.uk/rsd-engineeringcourse/)
+ + [Software carpentry courses](https://software-carpentry.org/lessons/)
+ + [Exercism](https://exercism.org/tracks/python)
+
+## Troubleshooting tips {.smaller .incremental}
+* Make sure the correct environment is activated
+ + `which python` or `which pip` to check
+* Check the scope of your variable
+* Pay attention to whether an operation is `in_place` or not
+ + Especially with `pandas` and `numpy`
+* Structure your code as a package as soon as you can
+ + `pyproject.toml` → more on this in future sessions!
+* Look out for deep vs shallow copies
+ + Especially with collections (lists, dicts, sets)
+
+## Question
+::::: {.columns}
+::: {.column .incremental width="55%"}
+* What does this return?
+:::
+
+::: {.column width="45%"}
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+a = [1, 2, 3]
+b = a
+b[0] = 42
+
+print(a[0])
+```
+::::
:::
+:::::
-## Use a variable several times
-Variables defined in the metadata is re-useable anywhere
+## Troubleshooting tips {.smaller .incremental}
+* Read the error messages!
+ + Google is your friend
+ + LLMs can help too
+ + Ask a colleague or contact us
+* Use a debugger (e.g. `pdb`, or IDE built-in debuggers)
+* Write tests for your code (e.g. using `pytest`)
-* {{< meta my-custom-stuff.my-reuseable-variable >}}
-* {{< meta my-custom-stuff.my-reuseable-variable >}}
diff --git a/requirements.txt b/requirements.txt
index d4424c2..7d02dd9 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,2 +1,5 @@
jupyter
jupyter-cache
+matplotlib
+numpy
+pandas
diff --git a/slides/basics.qmd b/slides/basics.qmd
new file mode 100644
index 0000000..5ae84ca
--- /dev/null
+++ b/slides/basics.qmd
@@ -0,0 +1,660 @@
+## Variables {.smaller}
+
+::: {.incremental}
+* A variable is a name that refers to a value
+* You can use variables to store data in memory
+* You can change the data stored in a variable at any time
+* Allows you to reuse data without having to retype it
+:::
+
+## Variables
+```{python}
+#| echo: true
+#| output-location: fragment
+a = 1
+print("The value of a is:")
+print(a)
+```
+
+
+
+```{python}
+#| echo: true
+#| output-location: fragment
+b = 2
+c = a + b
+print("The value of c is:")
+print(c)
+```
+
+## Variables
+```{python}
+#| echo: true
+#| output-location: fragment
+b = 2
+a = b
+print("The value of a is now:")
+print(a)
+```
+
+
+
+```{python}
+#| echo: true
+#| output-location: fragment
+x, y = 10, 20
+
+print("The value of x is:")
+print(x)
+print("The value of y is:")
+print(y)
+```
+
+## Data types
+::: {.fragment .incremental .smaller}
+* Different kinds of data are stored in different ways in memory
+* You can use the `type()` function to find out what type of data a variable contains
+:::
+
+::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+print(type(1))
+print(type(1.0))
+print(type("Hello"))
+```
+
+:::
+
+## Strings
+
+::: {.incremental}
+* A string is a sequence of characters
+* Strings are enclosed in single or double quotes
+* Used to represent text
+:::
+
+## Strings
+```{python}
+#| echo: true
+#| output-location: fragment
+
+a = "I want to learn "
+b = 'Python!'
+
+print(type(a))
+
+print(a + b)
+```
+
+
+
+::: {.fragment .fade-in style="font-size: 80%;"}
+* Algebraic operations can have different meanings for different data types
+ + Try `a*3` in the code above
+:::
+
+## f-strings
+::: {.incremental}
+* f-strings are a way to format strings
+* Use `f` before the string
+* Use `{variable}` to insert variables into the string
+:::
+::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+a = 42
+print(f"The value of a is: {a}")
+```
+:::
+
+## f-strings
+
+You can also use f-strings to format numbers
+
+::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+print(f"A rounded number: {8.333333333333333:.2f}")
+```
+:::
+
+::: {.fragment .fade-in}
+And compute values in the string
+```{python}
+#| echo: true
+#| output-location: fragment
+print(f"7 / 3 is: {7 / 3:.2f}")
+```
+:::
+
+## Question
+
+::: {.incremental}
+* What is the type of...?
+ + `100`
+ + `'Dog'`
+ + `3.14`
+ + `print`
+ + `False`
+ + `'50'`
+ + `None`
+:::
+
+## Question
+* Can you add an **int** and a **float**?
+
+::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+print(1 + 2.1)
+```
+
+:::
+
+::: {.fragment .fade-in}
+* What about an **int** and a **str**?
+
+```{python}
+#| echo: true
+#| output-location: fragment
+#| error: true
+print(1 + '2')
+```
+
+:::
+
+## Lists {.smaller}
+
+::::: {.columns}
+::: {.column .incremental width="55%"}
+* Ordered collection of values
+* Enclosed in square brackets `[]`
+* Any data type, including mixed types
+* Indexed from 0 with square brackets `[]`
+* Mutable (can be changed)
+:::
+
+::: {.column width="45%"}
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+my_list = [1, 2.0, 'three', 'dog']
+print(type(my_list))
+```
+::::
+
+
+
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+first = my_list[0]
+second = my_list[1]
+last = my_list[-1]
+print(first, second, last)
+```
+::::
+
+
+
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+my_list.append('cat')
+print(my_list)
+```
+::::
+
+
+
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+my_list[-1] = 'new_element'
+print(my_list)
+```
+::::
+:::
+:::::
+
+## Question {.smaller}
+::::: {.columns}
+::: {.column width="55%"}
+
+:::: {.fragment .fade-in fragment-index=1}
+* Find the third element from `my_list`?
+::::
+
+:::: {.fragment .fade-in fragment-index=4}
+* Change the second element to `3.0`?
+::::
+
+:::: {.fragment .fade-in fragment-index=6}
+* What happens if you request `my_list[5]`?
+::::
+
+:::
+
+::: {.column width="45%"}
+:::: {.fragment .fade-in fragment-index=2}
+```{python}
+#| echo: true
+print(my_list)
+```
+::::
+
+:::: {.fragment .fade-in fragment-index=3}
+```{python}
+#| echo: true
+print(my_list[2])
+```
+::::
+
+:::: {.fragment .fade-in fragment-index=5}
+```{python}
+#| echo: true
+my_list[1] = 3.0
+print(my_list)
+```
+::::
+
+:::: {.fragment .fade-in fragment-index=7}
+```{python}
+#| echo: true
+#| error: true
+print(my_list[5])
+```
+::::
+
+:::
+:::::
+
+## Tuples {.smaller}
+::::: {.columns}
+::: {.column .incremental width="55%"}
+* Ordered collection of values
+* Enclosed in parentheses `()`
+* Any data type, including mixed types
+* Immutable (cannot be changed)
+* When would you use a tuple instead of a list?
+:::
+
+::: {.column width="45%"}
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+#| error: true
+my_tuple = (1, 2.0, 'cat', 'dog')
+
+print(type(my_tuple))
+print(my_tuple[2])
+
+my_tuple[2] = 'rabbit'
+```
+::::
+:::
+:::::
+
+## Unpacking {.smaller}
+::::: {.columns}
+::: {.column .incremental width="55%"}
+* Unpack a list or tuple into multiple variables
+* Convenient for reassigning variables
+:::
+
+::: {.column width="45%"}
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+my_tuple = (1, 2, 3, 4, 5)
+
+a, b, c, d, e = my_tuple
+print(a, b, c, d, e)
+```
+::::
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+a, *b, c = my_tuple
+print(a, b, c)
+print(type(b))
+```
+::::
+:::
+:::::
+
+## Question {.smaller}
+::::: {.columns}
+::: {.column width="55%"}
+
+:::: {.fragment .fade-in fragment-index=1}
+* How would you turn `my_list` into a tuple?
+::::
+
+:::: {.fragment .fade-in fragment-index=3}
+* How would you turn `my_tuple` into a list?
+::::
+
+:::
+
+::: {.column width="45%"}
+:::: {.fragment .fade-in fragment-index=1}
+```{python}
+#| echo: true
+my_tuple = (1, 2, 3, 4, 5)
+my_list = [1, 2.0, 'three', 'dog']
+```
+
+
+
+::::
+
+:::: {.fragment .fade-in fragment-index=2}
+```{python}
+#| echo: true
+my_list_as_tuple = tuple(my_list)
+print(type(my_list_as_tuple))
+```
+::::
+
+:::: {.fragment .fade-in fragment-index=4}
+```{python}
+#| echo: true
+my_tuple_as_list = list(my_tuple)
+print(type(my_tuple_as_list))
+```
+::::
+:::
+:::::
+
+## Dictionaries {.smaller}
+::::: {.columns}
+::: {.column .incremental width="45%"}
+* Collection of key-value pairs
+* Enclosed in curly braces `{}`
+* Keys are unique and immutable
+:::
+
+::: {.column width="55%"}
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+my_dict = {
+ 'a_number': 1,
+ 'number_list': [1, 2, 3],
+ 'a_string': 'string',
+ 5.0: 'a float key'
+}
+
+print(my_dict.keys())
+print(my_dict.values())
+print(my_dict.items())
+print(my_dict['a_string'])
+print(my_dict[5.0])
+```
+::::
+:::
+:::::
+
+## Question {.smaller}
+::::: {.columns}
+::: {.column width="45%"}
+* What does `my_dict['a_number']` return?
+:::
+
+::: {.column width="55%"}
+```{python}
+#| echo: true
+my_dict = {
+ 'a_number': 1,
+ 'number_list': [1, 2, 3],
+ 'a_string': 'string',
+ 'a_number': 2
+}
+```
+
+:::: {.fragment fragment-index=1}
+
+
+```{python}
+#| echo: true
+#| output-location: fragment
+print(my_dict['a_number'])
+```
+::::
+:::
+:::::
+
+## Loops {.smaller}
+::::: {.columns}
+::: {.column .incremental width="55%"}
+* Do the same thing multiple times
+ + E.g.analyse N images
+* Watch out for indentation!
+ + Python uses indentation to define blocks of code
+ + Usually 2-4 spaces (or a tab)
+:::
+
+::: {.column width="45%"}
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+
+for i in [0, 1, 2]:
+ print(i)
+```
+::::
+
+
+
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+
+for i in range(5):
+ print(i)
+```
+::::
+:::
+:::::
+
+## Loops {.smaller}
+::::: {.columns}
+::: {.column .incremental width="55%"}
+* You can loop over any iterable
+* Use `_` if you don't need the loop variable
+* `enumerate()` gives you the index and the value
+:::
+::: {.column width="45%"}
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+my_list = ['cat', 'dog', 'rabbit']
+for animal in my_list:
+ print("My pet is a: " + animal)
+```
+::::
+
+
+
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+for _ in range(3):
+ print("Hello!")
+```
+::::
+
+
+
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+for index, animal in enumerate(my_list):
+ print(f"My pet #{index} is a: {animal}")
+```
+::::
+:::
+:::::
+
+## Question {.smaller}
+::: {.fragment .fade-in }
+* Create a list of integers from 0 to 5
+* Use a loop to find the sum of the squares of the integers in the list
+* Print the result
+:::
+::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+
+numbers = [0, 1, 2, 3, 4, 5]
+sum_of_squares = 0
+
+for number in numbers:
+ sum_of_squares = sum_of_squares + number**2
+
+print(sum_of_squares)
+```
+:::
+
+
+
+::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+#| code-line-numbers: "|5|"
+numbers = [0, 1, 2, 3, 4, 5]
+sum_of_squares = 0
+
+for number in numbers:
+ sum_of_squares += number**2
+
+print(sum_of_squares)
+```
+:::
+
+## Conditional statements {.smaller}
+::::: {.columns}
+::: {.column .incremental width="55%"}
+* Execute code only if a condition is met
+* Use `if`, `elif` (else if), and `else`
+:::
+::: {.column width="45%"}
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+#| code-line-numbers: "|1|3|5|7,8|"
+
+porridge_temp = 45
+
+if porridge_temp < 40:
+ print("Too cold!")
+elif porridge_temp > 50:
+ print("Too hot!")
+else:
+ print("Just right!")
+```
+::::
+:::
+:::::
+
+## Question {.smaller}
+* Write a loop that goes through the numbers 0 to 10
+* For each number, print whether it is even or odd
+ + Hint: use the modulus operator `%` and `==` to check for evenness
+
+::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+#| code-line-numbers: "|1|2,3|4,5|"
+
+for i in range(11):
+ if i % 2 == 0:
+ print(f"{i} is Even")
+ else:
+ print(f"{i} is Odd")
+```
+:::
+
+## List comprehensions {.smaller}
+::::: {.columns}
+::: {.column width="55%"}
+
+:::: {.fragment .fade-in fragment-index=1}
+* Concise way to create lists
+* `[expression for item in iterable]`
+::::
+
+:::: {.fragment .fade-in fragment-index=4}
+* Can include conditions
+* `[expression for item in iterable if condition]`
+::::
+
+:::
+::: {.column width="45%"}
+:::: {.fragment .fade-in fragment-index=2}
+Classic:
+```{python}
+#| echo: true
+
+squares = []
+for num in range(5):
+ squares.append(num**2)
+print(squares)
+```
+
+::::
+:::: {.fragment .fade-in fragment-index=3}
+List comprehension:
+```{python}
+#| echo: true
+squares = [num**2 for num in range(5)]
+print(squares)
+```
+::::
+:::
+:::: {.fragment .fade-in fragment-index=5}
+With condition:
+```{python}
+#| echo: true
+#| output-location: fragment
+large_squares = [num**2 for num in range(10) if num > 3]
+print(large_squares)
+```
+::::
+:::::
+
+## Question {.smaller}
+* Create a list of square even numbers from 1 to 10 using a list comprehension
+
+::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+even_squares = [num**2 for num in range(1, 11) if num % 2 == 0]
+print(even_squares)
+```
+:::
+
diff --git a/slides/classes_and_objects.qmd b/slides/classes_and_objects.qmd
new file mode 100644
index 0000000..45b2e79
--- /dev/null
+++ b/slides/classes_and_objects.qmd
@@ -0,0 +1,88 @@
+## Objects {.smaller}
+::::: {.columns}
+::: {.column width="55%"}
+* Everything in Python is an object
+ + Integer, float, string, list, functions...
+* Objects have attributes and methods
+ + Attributes: properties of the object
+ + Methods: functions that belong to the object
+:::
+::: {.column width="45%"}
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+a_string = "penguin"
+print(a_string.capitalize())
+```
+::::
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+#| code-line-numbers: "|1|2,3|4|"
+file_obj = open("example.txt", "a")
+print(file_obj.name)
+print(file_obj.mode)
+file_obj.close()
+```
+::::
+:::
+:::::
+
+## Classes {.smaller}
+::::: {.columns}
+::: {.column .incremental width="55%"}
+* A class is a blueprint for creating objects
+* Defined using the `class` keyword
+* Can have attributes and methods (functions)
+* `__init__` method is called when an object is created
+* `self` refers to the instance of the class
+:::
+::: {.column width="45%"}
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+#| code-line-numbers: "|1|2,3|5|6|"
+
+class Animal():
+ def __init__(self, species):
+ self.species = species
+
+pingu = Animal("penguin")
+print(pingu.species)
+```
+:::::
+:::
+:::::
+
+## Exercise {.smaller}
+* Try adding a new attribute `noise` to the `Animal` class
+* Add a method `make_noise` that prints the noise of the animal
+
+```{.python}
+class Animal():
+ your code here
+
+pingu = Animal("penguin", "noot")
+pingu.make_noise() # Output: noot
+```
+
+## Exercise {.smaller}
+```{python}
+#| echo: true
+#| output-location: fragment
+#| code-line-numbers: "|2|2,4|6,7|"
+
+class Animal():
+ def __init__(self, species, noise):
+ self.species = species
+ self.noise = noise
+
+ def make_noise(self):
+ print(self.noise)
+
+pingu = Animal("penguin", "noot")
+pingu.make_noise()
+```
diff --git a/slides/documenting_your_code.qmd b/slides/documenting_your_code.qmd
new file mode 100644
index 0000000..059005f
--- /dev/null
+++ b/slides/documenting_your_code.qmd
@@ -0,0 +1,186 @@
+## Documenting your code {.smaller}
+::::: {.columns}
+::: {.column width="55%"}
+:::: {.fragment .fade-in fragment-index=1}
+* Aim to need as little documentation as possible
+::::
+:::: {.fragment .fade-in fragment-index=4}
+* Which is easier to understand?
+ + Use meaningful pronounceable names
+::::
+:::
+
+::: {.column width="45%"}
+:::: {.fragment .fade-in fragment-index=2}
+```{.python}
+f = 378
+c = 22
+fpc = f / c
+```
+::::
+
+
+
+:::: {.fragment .fade-in fragment-index=3}
+```{.python}
+foci_number = 378
+cell_number = 22
+foci_per_cell = foci_number / cell_number
+```
+::::
+:::
+:::::
+
+## Documenting your code {.smaller}
+::::: {.columns}
+::: {.column .incremental width="55%"}
+* Each function should do one thing
+* Give functions descriptive names
+* This is a form of documentation too!
+:::
+::: {.column width="45%"}
+:::: {.fragment .fade-in}
+```{.python}
+def analyse_well(well_data):
+ ...
+
+def save_analysed_well(well_data):
+ ...
+
+def process_single_well(well_data):
+ analysed = analyse_well(well_data)
+ save_analysed_well(analysed)
+
+def process_plate(plate_data):
+ for well_data in plate_data:
+ process_single_well(well_data)
+```
+::::
+:::
+:::::
+
+## Comments {.smaller}
+::::: {.columns}
+::: {.column .incremental width="55%"}
+* Sometimes the code isn't self-explanatory
+* Use comments to explain why, not what
+* Use `#` for single-line comments
+* Use triple quotes `"""` for multi-line comments or docstrings
+* What's wrong with this example?
+:::
+
+::: {.column width="45%"}
+:::: {.fragment .fade-in}
+```{.python}
+def analyse_image(image):
+ # run gaussian blur
+ image = gaussian(image)
+ # run otsu thresholding
+ image = otsu(image)
+ # run watershed
+ image = watershed(image)
+ return image
+```
+::::
+:::
+:::::
+
+## Comments
+::::: {.columns}
+::: {.column width="50%"}
+```{.python}
+def analyse_image(image):
+ # run gaussian blur
+ image = gaussian(image)
+ # run otsu thresholding
+ image = otsu(image)
+ # run watershed
+ image = watershed(image)
+ return image
+```
+:::
+
+::: {.column width="50%"}
+```{.python}
+def analyse_image(image):
+ # remove noise
+ image = gaussian(image)
+ # binarise cells
+ image = otsu(image)
+ # split merged cells
+ image = watershed(image)
+ return image
+```
+:::
+:::::
+
+## Docstrings {.smaller}
+::::: {.columns}
+::: {.column .incremental width="55%"}
+* Use docstrings to describe what a function does and how to use it
+* Detail inputs and outputs
+* Use triple quotes `"""` for docstrings
+:::
+
+::: {.column width="45%"}
+:::: {.fragment .fade-in}
+```{.python code-line-numbers="|2-16|2|4-10|12-15|"}
+def add_numbers(num_0, num_1):
+ """Adds two numbers together
+
+ Parameters
+ ----------
+ num_0 : float
+ First number to be added
+
+ num_1 : float
+ Second number to be added
+
+ Returns
+ -------
+ float
+ Sum of numbers
+ """
+ return num_0 + num_1
+```
+::::
+:::
+:::::
+
+## README files {.smaller}
+::::: {.columns}
+::: {.column .incremental width="55%"}
+* Single file, usually `README.md` or `README.txt`
+* Outlines most important information
+ + What the project does
+ + How to install
+ + How to use
+* Very useful for others (and future you!)
+:::
+
+::: {.column width="45%"}
+:::: {.fragment .fade-in}
+```{.plaintext code-line-numbers="|5|7-8|9-16|"}
+My awesome Python package
+Adam Tyson 2025-01-01
+code@adamltyson.com
+
+Analyses data from X, Y, Z
+
+To install:
+...
+Data requirements:
+...
+To run:
+...
+Output:
+...
+Troubleshooting:
+...
+
+```
+::::
+:::
+:::::
+
+
diff --git a/slides/errors_and_exceptions.qmd b/slides/errors_and_exceptions.qmd
new file mode 100644
index 0000000..22a1b3d
--- /dev/null
+++ b/slides/errors_and_exceptions.qmd
@@ -0,0 +1,219 @@
+## Errors and Exceptions {.smaller}
+* You've probably seen an error already
+* If not try these, why don't they work?
+
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+#| error: true
+
+import london
+```
+::::
+
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+#| error: true
+len(print)
+```
+::::
+
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+#| error: true
+print("Hello"
+```
+::::
+
+## SyntaxError {.smaller}
+* Something is wrong with the structure of your code, code cannot run
+* What's wrong below?
+
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+#| error: true
+
+print hello
+```
+::::
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+#| error: true
+ for i in [0, 1, 2]
+ print(i)
+```
+::::
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+#| error: true
+"hello" = some_string
+```
+::::
+
+## Exceptions {.smaller}
+* Something went wrong while your code was running
+* What's wrong in these examples?
+
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+#| error: true
+1 / 0
+```
+::::
+
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+#| error: true
+giraffe * 10
+```
+::::
+
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+#| error: true
+a_list = [1, 2, 3]
+a_list[5]
+```
+::::
+
+## Tracebacks {.smaller}
+* Help you find the source of the error
+* Read from the bottom up
+* Look for the last line that is *your* code
+* [Debugging manifesto](https://wizardzines.com/images/debugging-manifesto.pdf) :bug:
+
+## Tracebacks {.smaller}
+```{python}
+#| echo: true
+#| output-location: fragment
+#| code-line-numbers: "|7|4,5|1,2|"
+#| error: true
+
+def divide_0(x):
+ return x / 0
+
+def call_func(x):
+ y = divide_0(x)
+
+z = call_func(10)
+```
+
+## Handling exceptions {.smaller}
+* What if you know an error might happen?
+
+:::: {.fragment .fade-in fragment-index=2}
+```{python}
+#| echo: true
+#| error: true
+def divide(x, y):
+ print(x / y) # This might raise an error
+
+divide(10, 2)
+divide(10, 0)
+```
+::::
+
+## Handling exceptions {.smaller}
+* What if you know an error might happen?
+* You can handle it with `try` and `except`
+
+:::: {.fragment .fade-in fragment-index=4}
+```{python}
+#| echo: true
+#| output-location: fragment
+#| code-line-numbers: "|2|3,4|5,6|"
+def safe_divide(x, y):
+ try:
+ value = x / y
+ print(f"Result: {value}")
+ except:
+ print("Y cannot be zero!")
+safe_divide(10, 2)
+safe_divide(10, 0)
+```
+::::
+
+## Handling exceptions {.smaller}
+* What's the problem with this code?
+
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+#| error: true
+
+safe_divide("10", 2)
+```
+::::
+
+:::: {.fragment .fade-in}
+* Don't make your `except` too general!
+::::
+
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+#| error: true
+#| code-line-numbers: "|2|3,4|5,6|7,8|"
+
+def better_safe_divide(x, y):
+ try:
+ value = x / y
+ print(f"Result: {value}")
+ except ZeroDivisionError:
+ print("Y cannot be zero!")
+ except TypeError:
+ print("Both x and y must be numbers!")
+
+better_safe_divide(10, 2)
+better_safe_divide(10, 0)
+better_safe_divide("10", 2)
+```
+::::
+
+## Exercise {.smaller}
+* Write a function to return the length of the input
+* If the object has no length, return `None`
+
+:::: {.fragment .fade-in}
+```{.python}
+def safe_len(x):
+ # Your code here
+ pass
+```
+::::
+
+## Exercise {.smaller}
+```{python}
+#| echo: true
+#| output-location: fragment
+#| code-line-numbers: "|2,3|4,5|"
+#| error: true
+def safe_len(x):
+ try:
+ return len(x)
+ except TypeError:
+ return None
+
+print(safe_len("hello"))
+print(safe_len(42))
+print(safe_len([1, 2, 3]))
+```
\ No newline at end of file
diff --git a/slides/extra_slide.qmd b/slides/extra_slide.qmd
deleted file mode 100644
index 1d699ad..0000000
--- a/slides/extra_slide.qmd
+++ /dev/null
@@ -1,3 +0,0 @@
-## A slide imported from outside the qmd
-
-This will become an example about how to use a common slide deck for the group.
\ No newline at end of file
diff --git a/slides/first_script.qmd b/slides/first_script.qmd
new file mode 100644
index 0000000..681423f
--- /dev/null
+++ b/slides/first_script.qmd
@@ -0,0 +1,2 @@
+## Writing your first Python script
+* Placeholder slide
\ No newline at end of file
diff --git a/slides/functions.qmd b/slides/functions.qmd
new file mode 100644
index 0000000..5dffdd1
--- /dev/null
+++ b/slides/functions.qmd
@@ -0,0 +1,392 @@
+## Functions {.smaller}
+::::: {.columns}
+::: {.column width="60%"}
+* Rectified Linear Unit (ReLU)
+ + `f(x) = max(0, x)`
+
+```{python}
+import matplotlib.pyplot as plt
+import numpy as np
+
+def relu(x):
+ return max(0, x)
+
+x = np.linspace(-1, 1, 100)
+y = [relu(i) for i in x]
+
+ax = plt.gca()
+
+plt.plot(x, y, color='black', linewidth=2)
+ax.spines['left'].set_position('zero')
+ax.spines['bottom'].set_position('zero')
+ax.spines['right'].set_color('none')
+ax.spines['top'].set_color('none')
+ax.set_ylim(-0.5, 1)
+y_ticks = ax.yaxis.get_major_ticks()
+y_ticks[3].label1.set_visible(False) # Hide the 0 label on
+plt.show()
+```
+:::
+
+::: {.column width="40%"}
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+x = 5
+if x > 0:
+ y = x
+else:
+ y = 0
+print(y)
+```
+::::
+:::
+:::::
+
+## Functions {.smaller}
+:::: {.columns}
+::: {.column width="55%"}
+
+::: {.incremental}
+* Time consuming and error prone to repeat code
+* Functions allow you to:
+ + Reuse code
+ + Break problems into smaller pieces
+ + Scope defined by indentation
+* Defined using the `def` keyword
+* Can take inputs (arguments) and return outputs
+:::
+
+:::
+::: {.column width="45%"}
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+#| code-line-numbers: "|1|2-5|3,5|"
+def relu(x):
+ if x > 0:
+ return x
+ else:
+ return 0
+
+y = relu(0.2)
+print(y)
+
+print(relu(5))
+print(relu(-3))
+```
+::::
+:::
+:::::
+
+## Exercise {.smaller}
+:::: {.fragment}
+* Write a function to check that a password is at least 8 characters long
+* The function should take a string as input and return `True` if the password is long enough, and `False` otherwise
+* You can use the built-in `len()` function to get the length of a string
+::::
+
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+def is_valid_password(password):
+ # Your code here
+ pass
+```
+::::
+
+## Exercise
+```{python}
+#| echo: true
+#| output-location: fragment
+def is_valid_password(password):
+ if len(password) >= 8:
+ return True
+ else:
+ return False
+
+print(is_valid_password("longpassword"))
+print(is_valid_password("short"))
+```
+
+## Arguments {.smaller}
+:::: {.columns}
+::: {.column .incremental width="50%"}
+
+::: {.incremental}
+* Functions can take multiple arguments
+* Positional arguments
+ + Must be given in the correct order
+* Keyword arguments
+ + Can be given in any order
+ + Use the syntax `name=value`
+ + Must come after positional arguments
+* Default arguments
+ + Have a default value if not provided
+ + Must come after non-default arguments
+:::
+
+:::
+::: {.column width="50%"}
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+def print_sum(num1, num2):
+ my_sum = num1 + num2
+ print(my_sum)
+
+print_sum(3, 5) # Positional arguments
+print_sum(num2=5, num1=3) # Keyword arguments
+```
+::::
+:::
+:::::
+
+## Arguments {.smaller}
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+def list_animals(first="dog", second="cat", third="penguin"):
+ print(f"First animal: {first}")
+ print(f"Second animal: {second}")
+ print(f"Third animal: {third}")
+
+list_animals()
+```
+::::
+
+
+
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+#| error: true
+list_animals(second="elephant", first="cow")
+```
+::::
+
+
+
+:::: {.fragment .fade-in}
+``` {python}
+#| echo: true
+#| output-location: fragment
+#| error: true
+list_animals(second="lion", "tiger")
+```
+::::
+
+## Arguments {.smaller}
+``` {python}
+#| echo: true
+#| output-location: fragment
+#| error: true
+def list_animals(first="dog", second, third="penguin"):
+ print(f"First animal: {first}")
+ print(f"Second animal: {second}")
+ print(f"Third animal: {third}")
+
+list_animals()
+```
+
+## Exercise {.smaller}
+:::: {.fragment}
+* Write a function that takes two strings as arguments and prints the longer of the two strings
+* One of the arguments should have a default value of an empty string
+::::
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+
+def longer_string(str1, str2=""):
+ # Your code here
+ pass
+```
+::::
+
+## Exercise {.smaller}
+
+```{python}
+#| echo: true
+#| output-location: fragment
+#| code-line-numbers: "|1|2,4|"
+
+def longer_string(str1, str2=""):
+ if len(str1) > len(str2):
+ long_str = str1
+ else:
+ long_str = str2
+
+ print(long_str)
+
+print(longer_string("apple", "banana"))
+print(longer_string("apple"))
+```
+
+## Using `*` and `**` {.smaller}
+
+::: {.incremental .smaller}
+* `*` and `**` are upacking operators, useful to unpack tuples, lists and dictionaries
+* I't common to use `*` and `**` when calling functions
+* You might have seen the syntax `*args` and `**kwargs` before
+* This is just a convention to indicate that the function takes a variable number of arguments
+:::
+
+::: {.fragment}
+```{python}
+#| echo: true
+#| output-location: fragment
+def my_func(*args, **kwargs):
+ print(f"Unpacking list: {args}")
+ print(f"Unpacking dictionary: {kwargs}")
+
+my_list = [1, 2, 3, 4, 5]
+my_dict = {'a': 1, 'b': 2, 'c': 3}
+
+print("Unpacking list:")
+my_func(*my_list)
+
+print("Unpacking dictionary:")
+my_func(**my_dict)
+```
+:::
+
+## How are `*args` and `**kwargs` useful? {.smaller}
+:::: {.columns}
+::: {.column width="55%"}
+::: {.incremental .smaller}
+* `*args` and `**kwargs` are useful when you don't know how many arguments will be passed to the function
+:::
+
+:::
+::: {.column width="45%"}
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+def my_func(**kwargs):
+ if 'name' in kwargs:
+ print(f"Hello, {kwargs['name']}!")
+ else:
+ print("Hello, world!")
+
+my_func(name="John")
+my_func()
+my_func(name="Jane")
+```
+::::
+:::
+:::::
+
+## Return values {.smaller}
+::::: {.columns}
+::: {.column width="55%"}
+
+::: {.incremental .smaller}
+* Functions can return one or more values
+* Use the `return` keyword
+* A function can only return once
+* If no return statement is given, the function returns `None`
+:::
+
+:::
+::: {.column width="45%"}
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+def a_func(a, b):
+ print(a + b)
+
+def b_func(a, b):
+ return a + b
+```
+::::
+
+
+
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+
+a_func(10, 20)
+```
+::::
+
+
+
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+b_func(10, 20)
+```
+::::
+
+
+
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+result = b_func(10, 20)
+print(result)
+```
+::::
+:::
+:::::
+
+
+## Return values {.smaller}
+
+```{python}
+#| echo: true
+#| output-location: fragment
+
+def c_func(a, b, c):
+ sum1 = a + b
+ sum_all = a + b + c
+
+ return sum1, sum_all
+```
+
+
+
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+result1, result2 = c_func(1, 2, 3)
+print(result1)
+print(result2)
+```
+::::
+
+
+
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+result = c_func(1, 2, 3)
+print(type(result))
+print(result)
+```
+::::
+
+
+
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+_, result2 = c_func(1, 2, 3)
+print(result2)
+```
+::::
diff --git a/slides/loading_and_saving.qmd b/slides/loading_and_saving.qmd
new file mode 100644
index 0000000..7dcec8f
--- /dev/null
+++ b/slides/loading_and_saving.qmd
@@ -0,0 +1,86 @@
+## Writing files {.smaller}
+::::: {.columns}
+::: {.column width="55%"}
+:::: {.fragment .fade-in fragment-index=1}
+* Python can open files in various modes:
+ + 'r' - read (default)
+ + 'w' - write (create or overwrite)
+ + 'a' - append (adds to the end of the file)
+* Always close the file after you're done
+::::
+
+:::: {.fragment .fade-in fragment-index=3}
+* Use `with` to do this automatically
+::::
+:::
+
+::: {.column width="45%"}
+:::: {.fragment .fade-in fragment-index=2}
+```{python}
+#| echo: true
+new_file = open('example.txt', 'w')
+new_file.write('Hello, world!\n')
+new_file.close()
+```
+::::
+
+
+
+:::: {.fragment .fade-in fragment-index=4}
+```{python}
+#| echo: true
+#| output-location: fragment
+with open('example.txt', 'a') as new_file:
+ new_file.write("Appended line.\n")
+ new_file.write('Hello, world!\n')
+```
+::::
+:::
+:::::
+
+## Reading files {.smaller}
+::::: {.columns}
+::: {.column width="50%"}
+* Open file in read mode to get content
+* Files can also be read line by line
+:::
+
+::: {.column width="50%"}
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+with open('example.txt', 'r') as f:
+ contents = f.read()
+
+print(contents)
+```
+::::
+:::
+:::::
+
+## Demo
+* Write a script that saves the below data to a comma-separated file
+
+```{python}
+#| echo: true
+column_labels = "sample_id,speed,distance"
+samples = [(0, 12, 53), (1, 7, 23), (2, 15, 30)]
+```
+
+## Demo
+```{python}
+#| echo: true
+column_labels = "sample_id,speed,distance"
+samples = [(0, 12, 53), (1, 7, 23), (2, 15, 30)]
+
+with open("out.csv", "w") as file:
+ file.write(column_labels)
+ file.write("\n")
+
+ for sample in samples:
+ for value in sample:
+ file.write(str(value) + ",")
+ file.write("\n")
+```
+
diff --git a/slides/modules_and_packages.qmd b/slides/modules_and_packages.qmd
new file mode 100644
index 0000000..181deec
--- /dev/null
+++ b/slides/modules_and_packages.qmd
@@ -0,0 +1,278 @@
+## Standard library {.smaller}
+::::: {.columns}
+::: {.column .incremental width="55%"}
+* A module is a file containing Python code
+* A package is a collection of modules
+* The standard library is a set of modules that come with Python
+* It provides a wide range of functionality
+ + File I/O
+ + Time and date handling
+ + Math and statistics
+* Accessed using the `import` and `from` keywords
+:::
+
+::: {.column width="45%"}
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+import os
+
+print(os.getcwd())
+```
+::::
+
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+import math
+
+print(math.sqrt(16))
+```
+::::
+
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+from pathlib import Path
+print(Path.home())
+```
+::::
+:::
+:::::
+
+## Installing a third-party package
+::: {.callout-important}
+* In the terminal, not in the Python console!
+* Remember to activate the conda environment first
+:::
+
+::: {.fragment .fade-in}
+```bash
+conda activate python-intro
+pip install pandas
+```
+:::
+
+## Using packages {.smaller}
+::::: {.columns}
+::: {.column .incremental width="55%"}
+* `pandas` is a popular package for data science
+* Based on `DataFrames` (tables) and `Series` (columns)
+* You can access it using the `import` keyword
+:::
+
+::: {.column width="45%"}
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+import pandas
+
+data_frame = pandas.read_csv("out.csv")
+
+print(data_frame)
+```
+::::
+:::
+:::::
+
+## Importing packages {.smaller}
+::::: {.columns}
+::: {.column width="55%"}
+:::: {.fragment .fade-in fragment-index=1}
+* You can import all available functions in a package
+::::
+
+:::: {.fragment .fade-in fragment-index=3}
+* You can import a specific function from a package
+ + Use `from ... import ...`
+::::
+
+:::: {.fragment .fade-in fragment-index=5}
+* You can also rename the package or function using `as`
+ + Common convention for `pandas` is `pd`
+::::
+:::
+
+::: {.column width="45%"}
+:::: {.fragment .fade-in fragment-index=2}
+```{.python}
+import pandas
+
+df = pandas.read_csv("out.csv")
+```
+::::
+
+
+
+:::: {.fragment .fade-in fragment-index=4}
+```{.python}
+from pandas import read_csv
+
+df = read_csv("out.csv")
+```
+::::
+
+
+
+:::: {.fragment .fade-in fragment-index=6}
+```{.python}
+import pandas as pd
+
+df = pd.read_csv("out.csv")
+```
+::::
+:::
+:::::
+
+## Installing packages {.smaller}
+::::: {.columns}
+::: {.column width="70%"}
+Two main ways to install packages:
+
+:::: {.fragment .smaller .fade-in fragment-index=1}
+pip
+
+* Python's built-in package manager
+* Uses the Python Package Index (PyPI)
+```{.bash}
+pip install package_name
+```
+::::
+
+:::: {.fragment .smaller .fade-in fragment-index=3}
+conda
+
+* Uses multiple channels (`conda-forge` is best, avoid `defaults`)
+* Can install non-Python dependencies
+```{.bash}
+conda install package_name
+```
+::::
+:::
+
+::: {.column width="30%"}
+:::: {.fragment .fade-in fragment-index=2}
+{width="100%" fig-alt="PyPI logo"}
+::::
+
+
+
+
+:::: {.fragment .fade-in fragment-index=4}
+{width="100%" fig-alt="Conda logo"}
+::::
+:::
+:::::
+
+## Exercise
+::::: {.columns}
+::: {.column width="55%"}
+In your conda environment, using `pip`:
+
+:::: {.incremental style="font-size: 80%;"}
+* Install `numpy`
+* Uninstall `numpy`
+* Install `numpy` version `1.26.4`
+* Update the installation of `numpy`
+* Install `matplotlib` and `scikit-image` in one command
+* List all installed packages with `pip list`
+::::
+:::
+
+::: {.column width="45%"}
+:::: {.fragment .fade-in}
+```bash
+pip install numpy
+```
+::::
+
+
+
+:::: {.fragment .fade-in}
+```bash
+pip uninstall numpy
+```
+::::
+
+
+
+:::: {.fragment .fade-in}
+```bash
+pip install numpy==1.26.4
+```
+::::
+
+
+
+:::: {.fragment .fade-in}
+```bash
+pip install numpy -U
+```
+::::
+
+
+
+:::: {.fragment .fade-in}
+```bash
+pip install matplotlib scikit-image
+```
+::::
+:::
+:::::
+
+## Exercise
+::::: {.columns}
+::: {.column width="55%"}
+In your conda environment, using `conda`:
+
+:::: {.incremental style="font-size: 80%;"}
+* Install `scipy`
+* Install `scipy` version `1.11.1`
+* Update the installation of `scipy`
+* List all installed packages with `conda list`
+::::
+:::
+
+::: {.column width="45%"}
+:::: {.fragment .fade-in}
+```bash
+conda install scipy
+```
+::::
+
+
+
+:::: {.fragment .fade-in}
+```bash
+conda install scipy=1.11.1
+```
+::::
+
+
+
+:::: {.fragment .fade-in}
+```bash
+conda update scipy
+```
+::::
+:::
+:::::
+
+## Exercise {.smaller}
+::::: {.columns}
+::: {.column width="55%"}
+* Find and install a package for opening Word documents in Python
+:::
+
+::: {.column width="45%"}
+:::: {.fragment .fade-in}
+```bash
+pip install python-docx
+```
+::::
+:::
+:::::
diff --git a/slides/structuring_your_code.qmd b/slides/structuring_your_code.qmd
new file mode 100644
index 0000000..d6f893b
--- /dev/null
+++ b/slides/structuring_your_code.qmd
@@ -0,0 +1,103 @@
+## Structuring your code {.smaller}
+::: {.incremental}
+* As your code gets more complex, you will want to organise it into multiple files and folders
+* This makes it easier to find and reuse code
+* Python modules and packages help you do this
+* You can also use version control (e.g. Git) to manage changes to your code (covered in the good practices lecture)
+:::
+
+## Exercise {.smaller}
+::::: {.columns}
+::: {.column .incremental width="55%"}
+* Create a file `my_funcs.py` with the functions `square_add_10` and `print_twice`
+:::
+
+::: {.column width="45%"}
+:::: {.fragment .fade-in}
+```{python}
+#| echo: true
+def square_add_10(x):
+ y = x**2
+ y = y + 10
+ return y
+
+def print_twice(a_string):
+ print(a_string)
+ print(a_string)
+```
+::::
+:::
+:::::
+
+## Exercise {.smaller}
+::: {.incremental}
+* In a new file `analysis.py`, import the functions from `my_funcs.py` and use them
+* **Modularity promotes reuse!**
+:::
+
+::: {.fragment .fade-in}
+```{.python}
+from .my_funcs import square_add_10, print_twice
+```
+:::
+
+::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+result = square_add_10(5)
+print("Result:", result)
+
+print_twice("Hello, world!")
+```
+:::
+
+## Structuring your code {.smaller}
+::: {.incremental}
+* As your codebase grows, consider organizing files into directories
+:::
+
+::: {.fragment .fade-in}
+```{.python}
+from .utils.my_funcs import square_add_10, print_twice
+```
+:::
+
+::: {.fragment .fade-in}
+```{python}
+#| echo: true
+#| output-location: fragment
+result = square_add_10(5)
+print("Result:", result)
+print_twice("Hello, world!")
+```
+:::
+
+## Organisation {.smaller}
+::: {.incremental}
+* Organise your code like you would any other project
+* Use meaningful names for files and directories
+* Group related functionality together
+:::
+
+::: {.fragment .fade-in}
+```plaintext
+.
+├── python_project/
+│ ├── analysis/
+│ │ ├── align.py
+│ │ ├── fft.py
+│ │ ├── preprocessing.py
+│ │ └── register.py
+│ ├── IO/
+│ │ ├── load.py
+│ │ ├── save.py
+│ │ ├── tools
+│ │ ├── settings.py
+│ │ └── system.py
+│ └── visualisation/
+│ ├── preprocess.py
+│ └── visualisation.py
+└── README.md
+```
+:::
\ No newline at end of file
diff --git a/slides/virtual_environments.qmd b/slides/virtual_environments.qmd
new file mode 100644
index 0000000..dfef719
--- /dev/null
+++ b/slides/virtual_environments.qmd
@@ -0,0 +1,152 @@
+## Scientific Python ecosystem {.smaller}
+
+
+::: footer
+[Aaron Meurer, ‘Python Array API Standard’, SciPy 2023](https://www.youtube.com/watch?v=16rB-fosAWw)
+:::
+
+## Virtual environments {.smaller}
+
+::: {.incremental}
+- Many packages are continuously developed 🔁
+ - This means there will be breaking changes
+ - Some packages will require specific versions of other packages
+- Similar with Python versions
+ - New versions of Python may introduce new features or deprecate old ones
+:::
+
+## Virtual environments {.smaller}
+
+](https://imgs.xkcd.com/comics/python_environment.png){width="20%"}
+
+## Virtual environments {.smaller}
+
+::: {style="color: grey;"}
+- Many packages are continuously developed 🔁
+ - This means there will be breaking changes
+ - Some packages will require specific versions of other packages
+- Similar with Python versions
+ - New versions of Python may introduce new features or deprecate old ones
+:::
+
+::: {.fragment .fade-in}
+- How can we have **two versions** of the same package installed at the same time?
+- Virtual environments! :tada:
+:::
+
+## Virtual environments {.smaller}
+
+::: {.incremental}
+- A virtual environment is an isolated Python installation
+ - Each environment has its own Python version
+ - Each environment has its own set of installed packages
+- Popular tools to manage virtual environments:
+ - [`venv`](https://docs.python.org/3/library/venv.html) (built-in)
+ - [`conda`](https://docs.conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html) (Anaconda/Miniconda)
+ - [`uv`](https://docs.astral.sh/uv/pip/environments/)
+ - [`pixi`](https://pixi.sh/latest/workspace/environment/)
+:::
+
+## Conda environments {.smaller}
+
+::: {.incremental}
+- Conda is a `package manager` and `virtual environment manager`
+- Separates Python environments from each other (and from system Python)
+- Can also manage non-Python packages (e.g. R, C libraries, etc...)
+- Reproducible environments that can be shared with:
+ - Collaborators
+ - Readers of your paper
+ - Future you (HPC, etc...)
+:::
+
+## Install miniforge
+
+[Click here to go to the download page](https://conda-forge.org/download/)
+
+::: {.callout-caution collapse="true"}
+## Windows troubleshooting
+- Did you had a previous version of Anaconda installed? You might need to uninstall it first.
+- {width="25%"}
+- `conda not found`? Run `conda init` in your terminal.
+:::
+
+## Environment management
+
+In your terminal (use Anaconda Prompt on Windows):
+
+
+
+``` bash
+conda create --name python-intro python=3.13 notebook
+```
+
+
+
+::: {.fragment .fade-in style="font-size: 80%;"}
+- Creates a new environment named `python-intro`
+- Installs Python 3.13 and Jupyter Notebook in that environment
+:::
+
+## Environment management
+In your terminal (use Anaconda Prompt on Windows):
+
+
+
+``` bash
+conda activate python-intro
+```
+
+
+
+::: {.fragment .fade-in style="font-size: 80%;"}
+- Remember to activate the environment before using it!
+:::
+
+## Environment management
+In your terminal (use Anaconda Prompt on Windows):
+
+
+
+``` bash
+conda deactivate
+```
+
+
+
+::: {.fragment .fade-in style="font-size: 80%;"}
+- Deactivates the current environment
+:::
+
+## Environment management
+In your terminal (use Anaconda Prompt on Windows):
+
+
+
+``` bash
+conda env list
+```
+
+
+
+::: {.fragment .fade-in style="font-size: 80%;"}
+- Lists all conda environments on your system
+:::
+
+## Environment management
+In your terminal (use Anaconda Prompt on Windows):
+
+
+
+``` bash
+conda remove --name python-intro --all
+```
+
+
+
+::: {.fragment .fade-in style="font-size: 80%;"}
+- Deletes the `python-intro` environment and all its packages
+:::
+
+::: {.fragment .fade-in .callout-tip}
+[Conda cheat sheet](https://docs.conda.io/projects/conda/en/latest/user-guide/cheatsheet.html)
+:::
diff --git a/slides/working_with_python.qmd b/slides/working_with_python.qmd
new file mode 100644
index 0000000..2f7e16e
--- /dev/null
+++ b/slides/working_with_python.qmd
@@ -0,0 +1,41 @@
+## Ways of working with Python {.incremental}
+
+::: {.incremental}
+* `REPL` (Read-Eval-Print Loop), "interactive Python", "Python console"
+* `Jupyter notebooks` (.ipynb)
+* `Scripts` (.py)
+:::
+
+::: {.fragment .fade-in}
+Demo time! 🧑🏻💻👩🏻💻
+:::
+
+
+
+## IDEs: Integrated Development Environments {.smaller}
+
+::: {.incremental}
+* Used by most developers
+* Allows you to switch between the three main ways of working with Python
+* Within the same program you can:
+ + Edit → Text editor :pencil:
+ + Organise → File explorer :file_folder:
+ + Run → Console :computer:
+:::
+
+## Example IDEs
+
+::: {.fragment .fade-in}
+* [Visual Studio Code](https://code.visualstudio.com/download) - more customizable
+* [PyCharm](https://www.jetbrains.com/pycharm/download/) - optimized for Python
+* ...
+:::
+
+::: {.fragment .fade-in}
+You can click on the name to go to the download page.
+:::
+
+::: {.fragment .fade-in}
+Install the one you like most!
+Igor prefers PyCharm, Laura prefers VS Code.
+:::
\ No newline at end of file