Dr Charles Martin
Do: Answer a short questionnaire about this class (2 minutes)
age = 999Quantitative Analysis: find magnitude, amounts or size of something and make rigorous comparisons
Qualitative Analysis: find the nature of things, themes, patterns, stories
Talk: What’s wrong with each of these claims? (2 minutes, discuss with your neighbour)
Careful: here’s the problems
What do we do with quantitative data once we have some?
what’s the average of a set of values?
Example: [2, 2, 3, 4, 873]
Example shows that outliers mess up mean, so median is often more useful.
how spread out is a set of values?
max - minSimilarly to central measures, interquartile range is robust against weird outliers
The distribution of the data is how it is spread out and where it is bunched up.
This matters because statistical tests often assume data is normal so findings might be misleading.
First thing to do after loading it in. May not be the most helpful approach… but still important to check it’s not garbled and the columns make sense.
| interactive activities | attend in person | watch online | degree | time in CBR |
|---|---|---|---|---|
| 5 | 2 | 2 | undergraduate | 1-3 years |
| 3 | 5 | 1 | postgraduate | 3+ years |
| 4 | 5 | 1 | postgraduate | <1 year |
| 5 | 2 | 4 | undergraduate | <1 year |
| 4 | 3 | 1 | undergraduate | <1 year |
Second thing to do when loading up data for analysis, calculate:
Think: are these values what you expected? do they suggest any interesting points about your data?
| stat | interactive activities | attend in person | watch online |
|---|---|---|---|
| count | 75 | 75 | 75 |
| mean | 3.36 | 3.15 | 2.84 |
| std | 1.30 | 1.24 | 1.39 |
| min | 1 | 1 | 1 |
| 25% | 2 | 2 | 2 |
| 50% | 3 | 3 | 3 |
| 75% | 4 | 4 | 4 |
| max | 5 | 5 | 5 |
Third thing to do when loading data
If plots show something interesting then you can investigate.
You can get more plots into one plot. Good for surfacing contrasts or telling a story about the data graphically.
sns.set_theme(style="ticks", palette="Set2")
plt.figure(figsize=(10, 6))
sns.boxplot(data=survey_data, x='degree_program',
y='interactive_activities_likert',
hue='time_in_canberra',
medianprops={'linewidth': 2, 'color': 'black'})
plt.savefig('plots/fake_data_complex_boxplot.png',
bbox_inches='tight', dpi=300)
plt.show()Lots of ways to do data analysis:
In this class we’ll use Python, numpy, pandas, scipy, seaborn, and matplotlib as a default stack for data analysis (yes, libraries are a problem in python…)
Do: Did you miss the questionnaire at the start of class? Answer it now! (1 minute)
Do: Let’s do some data analysis — on your answers from the start of class!
Follow along (or try at home):
.csv file, but six tables stacked on top of
each other"" linepd.read_csv() on the whole file will not do
what we want!First rule of data analysis: look at the raw data before you load it.
HCI Lecture Interaction Questionnaire
I enjoy interactive activities in lectures.
Response,Via,Screen name,...,Created At
Agree,...,User: Web_06529,"",2025-08-17 22:44:30
Strongly Agree,...,User: Web_aa2e2,"",...
""
I usually attend lectures in person
Response,Via,Screen name,...,Created At
...
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
import scipy.stats as stats
from google.colab import files
upload = files.upload() # choose the PollEV .csv export
data_file = list(upload.keys())[0]pandas (tables),
seaborn (plots), matplotlib (plot plumbing),
scipy.stats (tests)files.upload() is Colab-only — no live data? the
notebook has a fallback cell that downloads the 2025 responses
insteadfrom io import StringIO
raw = open(data_file).read()
blocks = raw.split('\n""\n') # split on the "" lines
blocks[0] = blocks[0].split("\n", 1)[1] # drop the report title
frames = []
for block in blocks:
question, table = block.split("\n", 1) # question, then CSV
df = pd.read_csv(StringIO(table))
df["question"] = question.strip()
frames.append(df)
# long format: one row per answer, tagged with its question
responses = pd.concat(frames, ignore_index=True)responses = responses[["Response", "Screen name", "question"]]
short_names = {
"I enjoy interactive activities in lectures.": "enjoy_interactive",
"I usually attend lectures in person": "attend_in_person",
"I usually watch lectures online": "watch_online",
"Select the interactive activities you prefer:": "preferred_activity",
"What kind of degree program are you in?": "degree_program",
"How long have you lived in Canberra?": "time_in_canberra",
}
responses["question"] = responses["question"].replace(short_names)
responses.describe() # count / unique / top --- does it look right?Tidy data: one row per participant, one column per question.
pivot turns the long “one row per answer” table into a
wide one.dropna() keeps only participants who answered
everything — careful, that’s an analysis
choice, and you should report itlikert = ["Strongly Disagree", "Disagree", "Neutral",
"Agree", "Strongly Agree"]
for col in ["enjoy_interactive", "attend_in_person", "watch_online"]:
survey_data[col] = pd.Categorical(survey_data[col],
categories=likert, ordered=True)
survey_data[col] = survey_data[col].cat.codes + 1Agree < Disagree < Neutral < ... 🙃.cat.codes + 1 maps it to 1–5.describe() now gives real descriptive statistics for
the Likert columns (they’re numeric!)sns.boxplot(data=survey_data,
x="degree_program", y="enjoy_interactive",
medianprops={"linewidth": 2, "color": "black"})
# add a third variable with hue
sns.boxplot(data=survey_data,
x="degree_program", y="enjoy_interactive",
hue="time_in_canberra",
medianprops={"linewidth": 2, "color": "black"})hue= splits each box again by a second grouping — but
it gets crowded fast…survey_long = pd.melt(
survey_data,
id_vars=["degree_program", "time_in_canberra", "preferred_activity"],
value_vars=["enjoy_interactive", "attend_in_person", "watch_online"],
var_name="question", value_name="score")
sns.boxplot(data=survey_long, x="question", y="score",
hue="degree_program",
medianprops={"linewidth": 2, "color": "black"})melt is the inverse of the pivot in step
3: wide → longg = sns.FacetGrid(survey_long, col="time_in_canberra")
g.map_dataframe(sns.boxplot, x="question", y="score",
hue="degree_program",
medianprops={"linewidth": 2, "color": "black"})
g.add_legend(title="Degree")col= and hue= to ask a different
question of the same dataundergrad = survey_data[survey_data["degree_program"] ==
"Undergraduate student (Bachelor degree)"]["enjoy_interactive"]
postgrad = survey_data[survey_data["degree_program"] ==
"Postgraduate student (Master degree)"]["enjoy_interactive"]
t_stat, p_value = stats.ttest_ind(undergrad, postgrad)p < 0.05 counts as
significantA theme is a high level finding from qualitative analysis, but what that means can differ.
Who has a question?