Skip to content

Commit 32871b6

Browse files
committed
Small changes
1 parent 8045de4 commit 32871b6

3 files changed

Lines changed: 135 additions & 44 deletions

File tree

docs/11-performance.md

Lines changed: 76 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -45,25 +45,42 @@ def timed(func, *args, **kwargs):
4545

4646
# A slow approach: looping over rows
4747

48-
This function computes row means and variances using a loop.
48+
This function computes the eucledian distance for two arrays.
4949

5050
```python
51-
def calc_mean_and_var_slow(mat):
52-
means = []
53-
variances = []
51+
import math
5452

55-
for i in range(mat.shape[0]):
56-
row = mat.iloc[i, :]
57-
means.append(np.mean(row))
58-
variances.append(np.var(row, ddof=1))
53+
def euclid_distance_by_hand(v1, v2):
54+
if len(v1) != len(v2):
55+
raise ValueError(f"Length mismatch: {len(v1)} != {len(v2)}")
5956

60-
return {"means": means, "variances": variances}
57+
s = 0.0
58+
for k in range(len(v1)):
59+
diff = float(v1[k]) - float(v2[k])
60+
s += diff * diff
61+
62+
return math.sqrt(s)
63+
64+
65+
def distance_matrix_df(df, func):
66+
X = np.asarray(df)
67+
names = df.index
68+
n = X.shape[0]
69+
70+
D = np.zeros((n, n))
71+
72+
for i in range(n):
73+
for j in range(i,n):
74+
D[i, j] = func(X[i], X[j])
75+
D[j, i] = D[i, j]
76+
77+
return pd.DataFrame(D, index=names, columns=names)
6178
```
6279

6380
Time it:
6481

6582
```python
66-
(_, t_slow) = timed(calc_mean_and_var_slow, gmp_data)
83+
(_, t_slow) = timed(distance_matrix_df, hspc_data.iloc[range(200)], euclid_distance_by_hand )# first 200 genes of the whole data only
6784
print("slow:", t_slow)
6885
```
6986

@@ -74,77 +91,93 @@ print("slow:", t_slow)
7491
Pandas can do these operations in compiled code (fast).
7592

7693
```python
77-
def calc_mean_and_var_fast(mat):
78-
return {
79-
"means": mat.mean(axis=1),
80-
"variances": mat.var(axis=1, ddof=1)
81-
}
94+
def euclid_distance_vec(v1, v2):
95+
v1 = np.asarray(v1, dtype=float)
96+
v2 = np.asarray(v2, dtype=float)
97+
98+
return np.sqrt(np.sum((v1 - v2) ** 2))
99+
82100
```
83101

84102
Time it:
85103

86104
```python
87-
(_, t_fast) = timed(calc_mean_and_var_fast, gmp_data)
88-
print("fast:", t_fast)
105+
(_, t_faster) = timed(distance_matrix_df, hspc_data.iloc[range(200)], euclid_distance_vec )
106+
print("faster:", t_faster)
89107
```
90108

91109
You should usually see a big speedup.
92110

93111
---
94112

113+
But there are also specialized function that operate on the full matrix:
114+
115+
```python
116+
from scipy.spatial.distance import pdist, squareform
117+
118+
def vectorized_dist_mat(mat):
119+
"""
120+
mat: pandas DataFrame or numpy array (rows = observations, cols = features)
121+
returns: full (n x n) Euclidean distance matrix as numpy array
122+
"""
123+
X = np.asarray(mat, dtype=float)
124+
125+
# upper triangle (condensed form)
126+
upper = pdist(X, metric="euclidean")
127+
128+
# convert to full symmetric matrix
129+
D = squareform(upper)
130+
131+
return D
132+
```
133+
134+
I am sure by now you can check the run time without my help.
135+
136+
95137
# The key lesson
96138

97139
For numerical work:
98140

99141
✅ Prefer **built-in** pandas/NumPy methods
100142
❌ Avoid Python loops over rows/columns
101143

144+
If not 100% sure you know all about the possible libraries you could use I recommend asking an AI tool for the max speed up in your function.
145+
102146
---
103147

104148
# What about pandas apply?
105149

106150
In R, `apply()` can be a speed trick.
107151
In Python, `DataFrame.apply()` usually still runs a Python function once per row/column.
108152

109-
That means it often behaves like a loop (and can be slow).
110-
111-
Example task:
153+
That means it often behaves like a loop (and can be slow). But eucledian distance is probably the worst example for apply as it works on one row/column only.
154+
So for this we switch to a simple mean calculation.
112155

113-
For each row, compute:
114-
115-
```
116-
(mean * std) / sum
156+
```python
157+
def mean_vec(vec1):
158+
return vec1.mean() # very fast here
117159
```
118160

119-
---
120-
121-
# Fast (vectorized) solution
122-
123161
```python
124-
means = hspc_data.mean(axis=1)
125-
stds = hspc_data.std(axis=1, ddof=1)
126-
sums = hspc_data.sum(axis=1)
127-
128-
result_vectorized = (means * stds) / sums
162+
(_, t_vec) = timed( hspc_data.apply, mean_vec , axis=1)
163+
print("apply time:", t_vec)
129164
```
130165

131-
Time it:
166+
This is rather fast, but using the inbuild pandas mean function is even faster:
167+
132168

133169
```python
134-
(_, t_vec) = timed(lambda: (hspc_data.mean(axis=1) * hspc_data.std(axis=1, ddof=1)) / hspc_data.sum(axis=1))
135-
print("vectorized:", t_vec)
170+
(_, t_vec) = timed( hspc_data.mean, axis=1)
171+
print("pandas time:", t_vec)
136172
```
137173

138-
---
174+
If we would have our data as a numpy array instead we could get this even faster using numpy's vectorization:
139175

140-
# Slower solution using apply
141176

142177
```python
143-
def example_func(v):
144-
return (np.mean(v) * np.std(v, ddof=1)) / np.sum(v)
145-
146-
(_, t_apply) = timed(hspc_data.apply, example_func, axis=1)
147-
print("apply:", t_apply)
178+
arr = np.array(hspc_data)
179+
(c, t_fast) = timed(arr.mean, axis=1 )
180+
print("numpy:", t_fast)
148181
```
149182

150183
---

docs/15-kmeans-project.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ One simple idea is the average within-cluster pairwise distance.
168168
For clusters \(C_1, \ldots, C_K\):
169169

170170
\[
171-
E = \frac{1}{K}\sum_{k=1}^{K} \left( \frac{1}{|C_k|(|C_k|-1)} \sum_{i \in C_k} \sum_{j \in C_k, j \neq i} d_{ij} \right)
171+
E = \frac{1}{K}\sum_{k=1}^{K} \left( \sum_{i \in C_k} \sum_{j \in C_k, j \neq i} d_{ij} \right)
172172
\]
173173

174174
Lower energy means tighter clusters.
@@ -243,3 +243,12 @@ Before you start:
243243
- Remember: the goal is learning, not competing with libraries
244244

245245
You now have the tools to tackle a real algorithmic problem in Python.
246+
Use Google, but no AI tool to learn e.g. how to accept user input in a python script.
247+
248+
# Performance
249+
250+
Once you have your script up and runnig and the results look accetable (plots!) and you still have some juce to program more:
251+
252+
1. Store the disnatnce matrix and do nor re-calculate
253+
2. Store the per cluster energies and only re-caulaulte the ones affected by the move
254+
3. Only calculate a delta energy and not touch the stable genes in the clusters
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
#how_to_parser_user_input.py
2+
# Source - https://stackoverflow.com/a
3+
# Posted by JoErNanO, modified by community. See post 'Timeline' for change history
4+
# Retrieved 2026-01-28, License - CC BY-SA 3.0
5+
6+
#!/usr/bin/python
7+
# coding: utf-8
8+
9+
import argparse
10+
11+
def parseArguments():
12+
# Create argument parser
13+
parser = argparse.ArgumentParser()
14+
15+
# Positional mandatory arguments
16+
parser.add_argument("creditMom", help="Credit mom.", type=float)
17+
parser.add_argument("creditDad", help="Credit dad.", type=float)
18+
parser.add_argument("debtMom", help="Debt mom.", type=float)
19+
20+
# Optional arguments
21+
parser.add_argument("-dD", "--debtDad", help="Debt dad.", type=float, default=1000.)
22+
parser.add_argument("-s", "--salary", help="Debt dad.", type=float, default=2000.)
23+
parser.add_argument("-b", "--bonus", help="Debt dad.", type=float, default=0.)
24+
25+
# Print version
26+
parser.add_argument("--version", action="version", version='%(prog)s - Version 1.0')
27+
28+
# Parse arguments
29+
args = parser.parse_args()
30+
31+
return args
32+
33+
def example(credit_mom, credit_dad, debt_mom, debt_dad = 1000, salary = 2000, bonus = 0):
34+
total_gain = salary + credit_dad + credit_mom + bonus
35+
total_loss = debt_dad + debt_mom
36+
37+
return total_gain - total_loss
38+
39+
if __name__ == '__main__':
40+
# Parse the arguments
41+
args = parseArguments()
42+
43+
# Raw print arguments
44+
print("You are running the script with arguments: ")
45+
for a in args.__dict__:
46+
print(str(a) + ": " + str(args.__dict__[a]))
47+
48+
# Run function
49+
print(example(args.creditMom, args.creditDad, args.debtMom, args.debtDad, args.salary, args.bonus))

0 commit comments

Comments
 (0)