diff --git a/_quarto.yml b/_quarto.yml index a196151a9b7a993f8fee7f03566c53ca8acbbea8..fd031fa15534a068f976979ff2dc2d19f6b63ddc 100644 --- a/_quarto.yml +++ b/_quarto.yml @@ -33,7 +33,7 @@ website: - "lectures/complexity/slides.qmd" - "lectures/debugging-strategies/slides.qmd" # - "lectures/good-scientific-practice/slides.qmd" - # - "lectures/user-experience/slides.qmd" + - "lectures/user-experience/slides.qmd" # - "lectures/testing/slides.qmd" # - "lectures/git2/slides.qmd" # - "lectures/parallelism/slides.qmd" @@ -49,7 +49,7 @@ website: - "exercises/complexity.qmd" - "exercises/debugging-strategies.qmd" # - "exercises/good_scientific_practice.qmd" - # - "exercises/user_experience.qmd" + - "exercises/user-experience.qmd" # - "exercises/testing.qmd" # - "exercises/git2.qmd" # - "exercises/parallelism.qmd" diff --git a/exercises/user-experience.qmd b/exercises/user-experience.qmd new file mode 100644 index 0000000000000000000000000000000000000000..bb0357bea4c018e737f7eda3c25e13ce04549fb8 --- /dev/null +++ b/exercises/user-experience.qmd @@ -0,0 +1,15 @@ +--- +title: "User experience design" +--- + +**1. Compare the user experience for accessing dropsonde datasets from two different campaigns and name 5 good/bad points that relate to one or the other dataset.** + +* Dropsondes from the EUREC4A measurement campaign: [https://howto.eurec4a.eu/dropsondes.html](https://howto.eurec4a.eu/dropsondes.html) +* Dropsondes from the NARVAL2 measurement campaign: [https://www.wdc-climate.de/ui/entry?acronym=HALO_measurements_3](https://www.wdc-climate.de/ui/entry?acronym=HALO_measurements_3) + +Get the data, open the datasets and make a simple plot of e.g. the humidity profile of the first dropsonde of the campaign. +What are the main or most striking differences when working with the datasets? + +**2. Take two of your own scripts, check them in, optimize their behavior, and make additional commits fixing the behavior. Explain how you improved them in the commit messages.** + +If you don't have any scripts of your own, take [The dropsonde comparison](https://easy.gems.dkrz.de/Processing/healpix/joanne_comparison.html) and another script of your choice from [https://easy.gems.dkrz.de/Processing/healpix/index.html](https://easy.gems.dkrz.de/Processing/healpix/index.html) and turn them into scripts you can call from the command line with sensible arguments and exceptions in case of bad arguments. diff --git a/lectures/user-experience/slides.qmd b/lectures/user-experience/slides.qmd new file mode 100644 index 0000000000000000000000000000000000000000..6cb7c735ebdeb56acc584bc27411a9b8ca2efa56 --- /dev/null +++ b/lectures/user-experience/slides.qmd @@ -0,0 +1,512 @@ +--- +title: "User Experience Design" +author: "Theresa Mieslinger and Florian Ziemen" +--- + +# Elements of UX design + +## Definition +> **User experience (UX)** is how a user interacts with and experiences a product, system or service. It includes a person's perceptions of **utility**, **ease of use**, and **efficiency**. -Wikipedia + +## Where do you encouter UX design in your everyday life? {.special} + +::: {.notes} +* app handling / interaction with touch screens +* examples: car, step length in stairs, take a foto of the MPIM door opener board :) +* standards, consistency, logical flow +* if intuition matches reality +* software example: a config parameter "enable_convection" in ICON should turn on convection everywhere (!) not only in atm +::: + +## Overview +* user research +* visual design +* interaction design / usability +* information architecture + +# User Research + +## Definition +> User research focuses on understanding user behaviors, needs and motivations through interviews, surveys, usability evaluations and other forms of feedback methodologies. - Wikipedia + +## Who are typical users? +::: {.fragment} +* you! +* your peers +* reviewers of a paper +* other scientists +* (including yourself in the future) +::: + + +## Utility +a user actually wants to have a product + +## User research tools +* talk to users +* make a survey +* create Personas + +# Visual design + +## Colors {.leftalign} +* use common color codes (e.g. <span style="color:green;">green = OK</span>, <span style="color:red;">red = Problem</span>) +* be inclusive and dont't fully rely on colors + +](static/gui_example.png){width="50%"} + +::: {.smaller} +see also [Apple Design Guidelines](https://developer.apple.com/design/human-interface-guidelines/color) +::: + +::: {.notes} +* maybe you all got it right away that the blue font markes a link to the doku ;-) +::: + +## Typography {.leftalign} +> Typography is the art and technique of arranging type to make written language legible, readable and appealing when displayed. [- Wikipedia](https://en.wikipedia.org/wiki/Typography) + +* Use readable fonts and fontsize + +## Layout {auto-animate=true} +```{python} +import matplotlib.pylab as plt +import numpy as np + +x = np.linspace(1, 25, 100) +plt.figure(dpi=200) +plt.plot(x, np.ones_like(x), label=r"$\mathcal{O}(1)$", ls=(0, (5, 10)), color="C4") +plt.plot(x, 1+np.log(x), label=r"$\mathcal{O}(\log{n})$", ls="-.", color="C3") +plt.plot(x, x, label=r"$\mathcal{O}(n)$", ls=":", color="C2") +plt.plot(x, 1 + x * np.log(x), label=r"$\mathcal{O}(n \log{n})$", color="C1") +plt.plot(x, x**2, label=r"$\mathcal{O}(n^2)$", ls="--", color="C0") +plt.xlabel("Number of elements n", fontsize=14) +plt.ylabel("Time or Space", fontsize=14) +plt.xlim(1, 25) +plt.ylim(0, 25) +plt.legend(fontsize=16) +plt.grid(True) +None + +``` +::: {.notes} +* data to ink ratio +* intuitive line color and their arrangement +::: + +## Layout {auto-animate=true} +```{python} +import matplotlib.pylab as plt +import numpy as np + +x = np.linspace(1, 25, 100) +plt.figure(dpi=200) +plt.plot(x, x**2, label=r"$\mathcal{O}(n^2)$", ls="--", color="C0") +plt.plot(x, 1 + x * np.log(x), label=r"$\mathcal{O}(n \log{n})$", color="C1") +plt.plot(x, x, label=r"$\mathcal{O}(n)$", ls=":", color="C2") +plt.plot(x, 1+np.log(x), label=r"$\mathcal{O}(\log{n})$", ls="-.", color="C3") +plt.plot(x, np.ones_like(x), label=r"$\mathcal{O}(1)$", ls=(0, (5, 10)), color="C4") +plt.xlabel("Number of elements n", fontsize=14) +plt.ylabel("Time or Space", fontsize=14) +plt.xlim(1, 25) +plt.ylim(0, 25) +plt.xticks([], []) +plt.yticks([], []) +plt.legend(fontsize=16) +plt.gca().spines[['right', 'top']].set_visible(False) +None + +``` +## Layout + + +::: {.notes} +* you expect a navigation bar at the top or left +* main content is catchy and easy to grasp and provides a link to continue. It leads to the page a user should read next or might be most interested to check out next. +* upper right corner: links to source code / community / outreach platforms +::: + +## Templates +* if you don't want to spend time, use a template / theme +* e.g. Sphinx, jupyter-book, quarto, HUGO, matplotlib themes + +# Interaction Design / Usability + +## Interaction design + +***imagine** how good interaction could be* + +* **goal-oriented design** - satisfying the needs and desires of the user +* **cognitive dimensions** - a framework to evaluate design solutions + +:::{.notes} +* opposed to goal-oriented design: interfaces can be designed to serve the needs of the service/product provider. User needs may be poorly served by this approach. +* how intuitive is a design solution? A/B testing +* what's the mental load? +::: + +## Example: Git +Git has a super nice interaction design! + +``` {bash} +git --help +usage: git [-v | --version] [-h | --help] [-C <path>] [-c <name>=<value>] + [--exec-path[=<path>]] [--html-path] [--man-path] [--info-path] + [-p | --paginate | -P | --no-pager] [--no-replace-objects] [--bare] + [--git-dir=<path>] [--work-tree=<path>] [--namespace=<name>] + [--config-env=<name>=<envvar>] <command> [<args>] + +These are common Git commands used in various situations: + +start a working area (see also: git help tutorial) + clone Clone a repository into a new directory + init Create an empty Git repository or reinitialize an existing one +... + +'git help -a' and 'git help -g' list available subcommands and some +concept guides. See 'git help <command>' or 'git help <concept>' +to read about a specific subcommand or concept. +See 'git help git' for an overview of the system. +``` + +## Example: function {auto-animate=true} +```{python} +#| echo: true +def is_atlantic(lon=0, lat=0): + return ((lon > 295) | (lon < 20)) & (lat > -30) & (lat < 30) +``` + +## Example: function {auto-animate=true} +```{python} +#| echo: true +def is_atlantic(lon, lat): + return ((lon > 295) | (lon < 20)) & (lat > -30) & (lat < 30) +``` +*use **sensible** defaults* + +## Example: function {auto-animate=true} +```{python} +#| echo: true +def is_tropical_atlantic(lon, lat): + return ((lon > 295) | (lon < 20)) & (lat > -30) & (lat < 30) +``` +*don't lie :)* + +## Example: function {auto-animate=true} +```{python} +#| echo: true +def is_tropical_atlantic(lon, lat): + lon = lon % 360 + return ((lon > 295) | (lon < 20)) & (lat > -30) & (lat < 30) +``` +*make it easy to do things right* + +## Example: function {auto-animate=true} +```{python} +#| echo: true +def is_tropical_atlantic(*, lon, lat): + lon = lon % 360 + return ((lon > 295) | (lon < 20)) & (lat > -30) & (lat < 30) +``` +*make bad usage difficult* + +::: {.notes} +* First! make it easy to use it correct: handle larger longitude range +* Second: make it harder to use wrong by adding "*" notation and assert latitude in range +* Defaults must be **sensible**, i.e. lat=0, lon=0 would be nonesense +::: + +## Interaction design: make bad usage difficult +**Which button opens the door?** + +{width="60%"} + +## Interaction design: don't lie + +**How do you get out?** + +{width="60%"} + +## Usability +You get a result and the software is<br/>intuitive and easy to work with. + +## Usability: Ease of use {.leftalign} +* intuition = function +* standards +* logical flow +* well-structured navigation system or documentation + +## Usability: Efficiency {.leftalign} +A result is there in + +* reasonable time +* using reasonable resources + +# Information architecture + +## Definition +> the art and science of structuring and organizing the information in products and services to support usability. - Wikipedia + +* the data model behind the interaction design + +# redesigning ICON output +*(as a UX design example)* + +## 1. User research: what are typical plots that scientists make? + +{width="60%"} + +## 2. Interaction design / Usability +*previous (unstructured) output* +``` +$ ls *.nc +ngc2009_atm_mon_20200329T000000Z.nc +ngc2009_oce_2d_1h_inst_20200329T000000Z.nc +ngc2009_atm_pl_6h_inst_20200329T000000Z.nc +ngc2009_lnd_tl_6h_inst_20200329T000000Z.nc +... + +$ ls *.nc | wc -l + 12695 +``` + +## 2. Interaction design / Usability +*optimizing output for the analysis* + +* provide an easy-to-understand overview +* only few steps necessary to get the data +* fast selection in time and space dimension + +```python +ds = cat.ICONrunID.to_dask() +t_hamburg = ds.tas.sel(...) +``` + +## 2. Interaction design / Usability +*build on existing user knowledge: a single **dataset*** +{width="60%"} + +::: {.notes} +* a dataset is a way to communicate information +* "normalised" dataset without redundant info, e.g. exactly one info on time and space +* balancing clarity and efficiency: sometimes duplicate information ("denormalised") can have a performance advantange, e.g. hierarchy in high-res model output + +* bad example: WALES flight altitude is time-dependent, but was a constant reference height. Bad variable name and misleading info +* -> the structure and type of the data / array holds important information +* best practice: limit only to the absolute necessary structure/info. +* e.g. a dataset with u, v arrays is easy to understand. If there is also ws (redundant), it is unclear whether this is exactly the same as (u**2 + v**2)**0.5 +::: + +## 3. Information architecture +* dataset holds pieces together +* chunks increase performance +* hierarchical dataset ("denormalized" to increase efficiency) +* HEALPix grid supports chunks & hierarchy<br/>(but users had to learn something new) + +:::{.samller} +*Further info on redesigned ICON output in [Tobi's EGU24 talk](https://tobi.pages.gwdg.de/egu2024/slides.html)* +::: + +# The benefits of UX design + +## Why should we care? +::: {.incremental} +* lower risk of wrong usage and wrong results +* it saves time and ressources and reduces information overload +* a positive user experience can lead to collaborations and more fun :) +::: + +::: {.notes} +* example: when running ICON scientists usually don't look at the source code, but merely the config file. You need to trust that parameters do the right thing. +::: + +# Specific guidlines in Earth System Informatics +## Standards +Standards are commonly known, they help guiding intuition, and also help implementors. + +* [POSIX](https://en.wikipedia.org/wiki/POSIX) (and GNU) +* [UNIX return codes](https://stackoverflow.com/questions/1101957/are-there-any-standard-exit-status-codes-in-linux) (e.g. 0 - no error, everything else - error) +* [CF conventions](https://cfconventions.org/) +* coding styles, e.g. PEP8 + +## File formats +use standard formats, preferrably machine readable + +* [YAML](https://yaml.org/), [JSON](https://www.json.org/), [netCDF](https://www.unidata.ucar.edu/software/netcdf/), [Zarr](https://zarr.readthedocs.io), ... +* YAML, JSON for machinereadable text file +* netCDF, Zarr for binary data + +::: {.notes} +* [NASA AMES FFI](https://espoarchive.nasa.gov/content/Ames_Format_Specification_v20#tth_sEc5.1) +::: + +# Input + +## Separate code and configuration +* **code** defines the algorithm +* **configuration** defines the input / boundary conditions / settings + +## What is configuration? {.special} + +::: {.notes} +* if you install microsoft word, your text (config) is not yet there. Default fonts are included, but if you have a special corporate design, you expect that to be separate +* +::: + +## Hierarchy of configurations +1. system config (`/etc`) +2. user config (`~/.config`) +3. project config (a file usually placed relative to current directory) +4. environment variables +5. CLI / API + +::: {.notes} +e.g. when a software is used, such as `conda`, it searches through a hierarchy of places combines the configuration parameters +::: + +## Parsing command line arguments +* [Utility Argument Syntax Guidlines](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html): + ```{bash} + utility_name [-a] [-b] [-c option_argument] [-d|-e] + [-f[option_argument]] [operand...] + ``` +* [GNU style syntax following POSIX guidelines](https://www.gnu.org/prep/standards/html_node/Command_002dLine-Interfaces.html) + * single-letter Unix-style, e.g. `-i` + * long-named option, e.g. `--input` ([list of known options](https://www.gnu.org/prep/standards/html_node/Option-Table.html#Option-Table)) + * recommended minimal options: `--version` and `--help` + +## Parsing command line arguments +* standard command line argument parsers + * [`getopt`](https://man7.org/linux/man-pages/man1/getopt.1.html) - program for parsing arguments in shell scripts + * `getopt` exists also in C/C++ and many other languages + * Python: e.g. [`argparse`](https://docs.python.org/3/library/argparse.html) + +## Code {auto-animate=true} + +```python +print("hello world") +``` + +## Code {auto-animate=true} + +```python +name = "world" +print(f"hello {name}") +``` + +## Code {auto-animate=true} + +```python +import sys + +name = sys.argv[1] +print(f"hello {name}") +``` + +## Code {auto-animate=true} + +```python +import sys + +name = sys.argv[1] +print(f"hello {name}") + +if "?" in sys.argv: + print("Pass in a name and I'll greet you.") +``` + +## Hands-on {.handson} +:::{.smaller} + +Rewrite [safe_copy.py](static/safe_copy.py) to use intuitive argument names, then make it more intuitive. +```python +{{< include static/safe_copy.py >}} +``` +::: +# While running + +## Escalate Errors {.special} +```python + if not os.access(args.outfIle, os.F_OK): + shutil.copyfile(args.sOurce, args.outfIle) + + [...] +``` +What could go wrong? + +## Raise exceptions +* Programming languages use exceptions to communicate problems. +* Functions further up in the call stack can then decide how to respond. +* Uncaught exceptions generally lead to a program exit. + +## Try / except / else in python +```python +def do_things(): + try: + res = do_risky_thing() + except ExceptionType as exc: + handle_exception(exc) + else: # optional, if no exception / ... in try: + work_with_result(res) + finally: #will be executed at the end, no matter what. + clean_up() + move_on_with_normal_flow() +``` + +## Clean up behind you +* If you open a file, make sure to close it, even in case of an exception. + +* Use the [with](https://docs.python.org/3/reference/compound_stmts.html#with) statement in python. + +## Write decent error messages +* Tell what happend why and where +* Exit with non-zero exit code +* Make sure things can't continue (if that's relevant) + +## Hands-on {.handson} +Take safe_copy.py and add useful error messages and return codes. + +# Output + +## stdout and stderr + +* stdout is for ouput somebody else might want to process + `grep bash /etc/bashrc | wc ` +* stderr is for *error* messages + `grep bash file_that_does_not_exist |wc` + +## Datasets +* a dataset is a way to communicate information +* clarity in the dataset structure is clear communication +* group things together that belong together +* follow standards + +## Dataset normalization +(normal as in orthogonal, not *the usual mess*) + +* Idea: Only provide information that is not already contained otherwise. +* Keeps your dataset clean +* Balance with the effort it takes to compute information that you optimized out. +* Provide scalars as scalars, not as 3D fields / ... +* Aggregation levels can be worth the extra data / effort + +# Take-home: take a user's perspective! {.special} + +* use standards +* be consistent and intuitive in your style +* "Don't lie" :) + +:::{.notes} +* don't return a result if an error occured +* don't change user input within the code without communicating the change +::: + +# Further reading +* Edward Tufte: *Beautiful Evidence* (and other books) + [UHH Library System](https://katalogplus.sub.uni-hamburg.de/vufind/Record/80685877X) +* *Dead programs tell no lies* (Topic 24 in Ch 4 of *The pragmatic programmer* + [UHH Library system](https://katalogplus.sub.uni-hamburg.de/vufind/Record/168729271X) | [MPS ebooks](https://ebooks.mpdl.mpg.de/ebooks/Record/EB001950880) | [German ebook via UHH](https://katalogplus.sub.uni-hamburg.de/vufind/Record/1755846843)) +* *Defensive Programming and “Fail Fastâ€* (in Ch 13 of *Fluent Python*). +* *with, match, and else Blocks* (Ch 18 of *Fluent Python*). + +# What's your UX with this lecture series? {.special} \ No newline at end of file diff --git a/lectures/user-experience/static/gui_example.png b/lectures/user-experience/static/gui_example.png new file mode 100644 index 0000000000000000000000000000000000000000..37441263c115115cfffde7d900e2cb64a2b4ef60 Binary files /dev/null and b/lectures/user-experience/static/gui_example.png differ diff --git a/lectures/user-experience/static/home_page_example.png b/lectures/user-experience/static/home_page_example.png new file mode 100644 index 0000000000000000000000000000000000000000..e23b37b0d61415fb1c65c2c67be5c4913308835e Binary files /dev/null and b/lectures/user-experience/static/home_page_example.png differ diff --git a/lectures/user-experience/static/icon_new_output.png b/lectures/user-experience/static/icon_new_output.png new file mode 100644 index 0000000000000000000000000000000000000000..47606e3546d64eeb424aef3a4e3d46de74cde879 Binary files /dev/null and b/lectures/user-experience/static/icon_new_output.png differ diff --git a/lectures/user-experience/static/icon_plot_examples.png b/lectures/user-experience/static/icon_plot_examples.png new file mode 100644 index 0000000000000000000000000000000000000000..a1afbd2bc1128a2246947086bcb701bb47db98b0 Binary files /dev/null and b/lectures/user-experience/static/icon_plot_examples.png differ diff --git a/lectures/user-experience/static/mpim_door.jpeg b/lectures/user-experience/static/mpim_door.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..157cddda282c4ce6f744dc70937189dbab2642f2 Binary files /dev/null and b/lectures/user-experience/static/mpim_door.jpeg differ diff --git a/lectures/user-experience/static/mpim_door_opener.jpeg b/lectures/user-experience/static/mpim_door_opener.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..c832b751704aaa57c4d460c258d2d50677e791af Binary files /dev/null and b/lectures/user-experience/static/mpim_door_opener.jpeg differ diff --git a/lectures/user-experience/static/safe_copy.py b/lectures/user-experience/static/safe_copy.py new file mode 100755 index 0000000000000000000000000000000000000000..b64e2b29ae652381420a4f14d2027d1d18146d65 --- /dev/null +++ b/lectures/user-experience/static/safe_copy.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python + +import argparse +import shutil +import os + + +def parse_args(): + parser = argparse.ArgumentParser() + parser.add_argument("-I", "--outfIle", help="Where to copy") + parser.add_argument("-O", "--sOurce", help="Source file") + args = parser.parse_args() + return args + + +def main(): + args = parse_args() + if not os.access(args.outfIle, os.F_OK): + shutil.copyfile(args.sOurce, args.outfIle) + + +if __name__ == "__main__": + main()