numpy_lecture.md 13.8 KB
Newer Older
Jochen Schulz's avatar
Jochen Schulz committed
1
2
3
4
5
6
7
8
9
10
11
---
jupytext:
  text_representation:
    extension: .md
    format_name: myst
kernelspec:
  display_name: Python 3
  language: python
  name: python3
---

12
13
14
15
16
17
# Numpy

Python Standard-Datenstrukturen sind flexibel, aber für numerische Operationen langsam. So können z.B. Listen eine variable Anzahl von Objekten beliebigen Types aufnehmen, die außerdem an beliebigen Stellen im Speicher abgelegt sein können. Der damit verbundene "Verwaltungs-Overhead" geht allerdings auf Kosten der Performance.

**Numpy** ist eine Bibliothek, die einen effizienten Array-Typen bereitstellt und Python so für numerische Anwendungen nutzbar macht.

Jochen Schulz's avatar
Jochen Schulz committed
18
```{code-cell}
19
20
21
22
23
24
25
26
27
import numpy as np
```

## Arrays

Arrays sind -- grob gesagt -- zusammenhängende Blöcke im Arbeitsspeicher, die Objekte eines einzelnen, meist numerischen Typs enthalten. Array-Operationen, z.B. element-weise Addition zweier Arrays, lassen sich effizient durchführen, u.a. da nur ein einziges Mal der Typ und der Speicherort bestimmt werden muss, nicht für jedes Element separat.

Konzeptionell sind Arrays ein- oder mehrdimensionale rechteckige (oder n-dimensionale Varianten davon) Anordnungen von Zahlen.

Nils Beyer's avatar
Nils Beyer committed
28
(arrays)=
Jochen Schulz's avatar
Jochen Schulz committed
29
**Erzeugen von Arrays aus Listen**
Jochen Schulz's avatar
Jochen Schulz committed
30
```{code-block}
31
32
33
34
35
np.array(liste)
```

erzeugt ein Array, dessen Elemente aus der `liste` kopiert werden.

Jochen Schulz's avatar
Jochen Schulz committed
36
```{code-cell}
37
38
39
40
41
42
43
44
45
a = np.array([0, 1, 2, 3])
print(type(a))
a
```

### Mehrdimensionale Arrays

Mehrdimensionale Arrays werden durch Übergabe einer verschachtelten Liste an `array` erzeugt. Die Liste sollte eine *rechteckige* Struktur haben.

Jochen Schulz's avatar
Jochen Schulz committed
46
```{code-cell}
47
48
49
50
a = np.array([[0, 1, 2, 3], [4, 5, 6, 7]])
a
```

Jochen Schulz's avatar
Jochen Schulz committed
51
**Dimension des Arrays**
52

Jochen Schulz's avatar
Jochen Schulz committed
53
```{code-block}
54
55
56
57
58
array_object.ndim
```

enthält die Dimension bzw. Anzahl der *Achsen* des Arrays.

Jochen Schulz's avatar
Jochen Schulz committed
59
```{code-cell}
60
61
62
a.ndim
```

Jochen Schulz's avatar
Jochen Schulz committed
63
**Shape**
64

Jochen Schulz's avatar
Jochen Schulz committed
65
```{code-block}
66
67
68
69
70
array_object.shape
```

ist ein Tupel, dessen Einträge die Größe des Arrays entlang der jeweiligen Achse angeben.

Jochen Schulz's avatar
Jochen Schulz committed
71
```{code-cell}
72
73
74
75
76
a.shape
```

**Achtung**: "Zeilen-" oder "Spaltenvektoren", also Arrays mit *shape* `(1, n)` oder `(n, 1)`, sind konzeptionell trotzdem 2-dimensional:

Jochen Schulz's avatar
Jochen Schulz committed
77
```{code-cell}
78
79
80
81
82
83
84
85
a = np.array([[1, 2, 3]])
print(a)
print(a.shape)
print(a.ndim)
```

## Erzeugung häufig verwendeter Arrays

Jochen Schulz's avatar
Jochen Schulz committed
86
**arange**
87

Jochen Schulz's avatar
Jochen Schulz committed
88
```{code-block}
89
90
91
92
93
np.arange(start=0, stop, step=1)
```

erzeugt eine *array range*; im Gegensatz zu `range` ist der Rückgabewert ein volles Array:

Jochen Schulz's avatar
Jochen Schulz committed
94
```{code-cell}
95
96
97
98
99
a = np.arange(4)
print(type(a))
a
```

Jochen Schulz's avatar
Jochen Schulz committed
100
**linspace**
101

Jochen Schulz's avatar
Jochen Schulz committed
102
```{code-block}
103
104
105
106
107
np.linspace(start, stop, npoints)
```

liefert äquidistante Punkte in einem gegebenen Intervall:

Jochen Schulz's avatar
Jochen Schulz committed
108
```{code-cell}
109
110
111
np.linspace(-1, 1, 5)
```

Jochen Schulz's avatar
Jochen Schulz committed
112
**zeros**
113

Jochen Schulz's avatar
Jochen Schulz committed
114
```{code-block}
115
116
117
118
119
np.zeros(shape)
```

erzeugt ein mit `0` initialisiertes Array einer gegebenen `shape`:

Jochen Schulz's avatar
Jochen Schulz committed
120
```{code-cell}
121
122
123
np.zeros((3, 3))
```

Jochen Schulz's avatar
Jochen Schulz committed
124
**ones**
125

Jochen Schulz's avatar
Jochen Schulz committed
126
```{code-block}
127
128
129
130
131
np.ones(shape)
```

erzeugt ein mit `1` initialisiertes Array:

Jochen Schulz's avatar
Jochen Schulz committed
132
```{code-cell}
133
134
135
np.ones((2, 3))
```

Jochen Schulz's avatar
Jochen Schulz committed
136
**eye**
137

Jochen Schulz's avatar
Jochen Schulz committed
138
```{code-block}
139
140
141
142
143
np.eye(dim)
```

erzeugt eine Identitäts-Matrix (immer 2d):

Jochen Schulz's avatar
Jochen Schulz committed
144
```{code-cell}
145
146
147
np.eye(3)
```

Jochen Schulz's avatar
Jochen Schulz committed
148
**diag**
149

Jochen Schulz's avatar
Jochen Schulz committed
150
```{code-block}
151
152
153
154
155
np.diag(vector, k=0)
```

erzeugt eine Diagonal-Matrix (immer 2d):

Jochen Schulz's avatar
Jochen Schulz committed
156
```{code-cell}
157
158
159
160
161
np.diag(np.array([3, 1, 4, 1]))
```

Mit `k != 0` wird eine Matrix mit dem gegebenen Vektor auf einer Nebendiagonalen erzeugt. Positive Werte bezeichnen die oberen Nebendiagonalen.

Jochen Schulz's avatar
Jochen Schulz committed
162
```{code-cell}
163
164
165
np.diag(np.array([3, 1, 4, 1]), 1)
```

Jochen Schulz's avatar
Jochen Schulz committed
166
```{code-cell}
167
168
np.diag(np.array([3, 1, 4, 1]), -2)
```
Nils Beyer's avatar
Nils Beyer committed
169
(np_indizierung)=
170
## Indizierung und Slices
Nils Beyer's avatar
Nils Beyer committed
171

172
173
Indizierung funktioniert ähnlich wie für Listen:

Jochen Schulz's avatar
Jochen Schulz committed
174
```{code-block}
175
176
177
array[start1:stop1:step1, start2:stop2:step2, ...]
```

Jochen Schulz's avatar
Jochen Schulz committed
178
```{code-cell}
179
180
181
182
183
184
a = np.arange(10)
(a[0], a[4], a[-1])
```

Slices sind ebenfalls möglich:

Jochen Schulz's avatar
Jochen Schulz committed
185
```{code-cell}
186
187
188
a[:7]
```

Jochen Schulz's avatar
Jochen Schulz committed
189
```{code-cell}
190
191
192
193
194
a[1::2]
```

Für mehrdimensionale Arrays werden entsprechend mehrdimensionale Indizes verwendet:

Jochen Schulz's avatar
Jochen Schulz committed
195
```{code-cell}
196
197
198
199
200
201
202
a = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])
print(a)
a[1, 0]
```

Slicing funktioniert auch in mehreren Dimensionen:

Jochen Schulz's avatar
Jochen Schulz committed
203
```{code-cell}
204
205
206
207
208
a[::2, 1:]
```

Slices können auch auf der linken Seite einer Zuweisung verwendet werden. Auf der rechten Seite sollte dann ein Array der gleichen Größe stehen.

Jochen Schulz's avatar
Jochen Schulz committed
209
```{code-cell}
210
211
212
213
214
a = np.arange(1, 10)
a[:3] = np.array([-1, -2, -3])
a
```

Nils Beyer's avatar
Nils Beyer committed
215
(stacking)=
216
217
218
219
220
## Stacking und Splitting
Arrays können entlang einer Achse zusammengesetzt werden:

- Zusammensetzen entlang einer neuen Achse. Alle Arrays müssen die gleiche `shape` haben:

Jochen Schulz's avatar
Jochen Schulz committed
221
    ```{code-block}
222
223
224
225
226
    np.stack(arrays, axis=0)
    ```

- Zusammensetzen entlang einer existierenden Achse. Alle Arrays müssen die gleiche `shape` haben, außer entlang der gegebenen `axis`:

Jochen Schulz's avatar
Jochen Schulz committed
227
    ```{code-block}
228
229
230
231
232
233
234
235
236
237
    np.concatenate(arrays, axis=0)
    ```

- Für häufig verwendete Fälle gibt es Spezialfunktionen:

    - `np.hstack(arrays)` setzt *horizontal* zusammen, d.h. für 1d-Arrays wie `concatenate(arrays, axis=0)`, für 2d und höher wie `concatenate(arrays, axis=1)`
    - `np.vstack(arrays)` setzt *vertikal* zusammen, d.h. für 1d-Arrays wie `stack(arrays, axis=0)`, für 2d und höher wie `concatenate(arrays, axis=0)`

- Aufteilen eines Arrays entlang einer Achse:

Jochen Schulz's avatar
Jochen Schulz committed
238
    ```{code-block}
239
240
241
242
243
244
245
246
    np.split(array, nparts, axis=0)
    np.split(array, (idx1, idx2, ...), axis=0)
    ```

    Die erste Form teilt das Array in `nparts` gleich große Teile oder liefert einen Fehler, falls das nicht möglich ist. Die zweite Form teilt das Array an den gegebenen Indizes.

    Die Funktion gibt ein Tupel zurück, d.h.

Jochen Schulz's avatar
Jochen Schulz committed
247
    ```{code-block}
248
249
250
251
252
    first, second, third = np.split(array, (4, 6))
    ```

    ist äquivalent zu

Jochen Schulz's avatar
Jochen Schulz committed
253
    ```{code-block}
254
255
256
257
258
259
260
261
262
    first = array[:4]
    second = array[4:6]
    third = array[6:]
    ```

## Kopien, Referenzen und Views

Zuweisungen von Arrays erzeugen, wie schon für andere Container-Typen, keine Kopie, sondern Referenzen.

Jochen Schulz's avatar
Jochen Schulz committed
263
```{code-cell}
264
265
266
267
268
269
270
271
a = np.arange(5)
b = a
b[2] = -1
a
```

Dasselbe gilt für Slices:

Jochen Schulz's avatar
Jochen Schulz committed
272
```{code-cell}
273
274
275
276
277
278
279
280
281
282
a = np.arange(5)
b = a[::2]
b[2] = -1
a
```

`b` ist hier ein **View** auf einen Teilbereich von `a`. Slicing-Operationen erzeugen immer *Views*.

Explizit lassen Views sich mit der `.view`-Methode erstellen; sie teilen die *Daten* des ursprünglichen Arrays, aber nicht die *Metadaten* (wie z.B. `shape`):

Jochen Schulz's avatar
Jochen Schulz committed
283
```{code-cell}
284
285
286
287
288
289
290
291
292
293
294
295
a = np.arange(6)
b = a.view()

b.shape = (2, 3)
a[0] = 10

print('a = \n', a)
print('b = \n', b)
```

Außerdem funktioniert die Parameter-Übergabe an Funktionen auch als Referenz, so dass Funktionen die Variablen des Aufrufers ändern können:

Jochen Schulz's avatar
Jochen Schulz committed
296
```{code-cell}
297
298
299
300
301
302
303
304
305
306
def f(x):
    x[2] = -1

a = np.arange(5)
f(a)
a
```

Für Arrays kann das insbesondere problematisch sein, da numerische Algorithmen oft *in-place* arbeiten, um effizienter zu sein.

Jochen Schulz's avatar
Jochen Schulz committed
307
**Kopieren von Arrays**
308

Jochen Schulz's avatar
Jochen Schulz committed
309
```{code-block}
310
311
312
313
314
315
np.copy(array_object)
array_object.copy()
```

Wird eine Kopie benötigt, muss diese mit der `copy`-Funktion explizit erzeugt werden.

Jochen Schulz's avatar
Jochen Schulz committed
316
```{code-cell}
317
318
319
320
321
322
323
324
a = np.arange(5)
b = a.copy()
b[2] = -1
a
```

## `reshape` und `transpose`

Jochen Schulz's avatar
Jochen Schulz committed
325
**Ändern der `shape` eines Arrays**
326

Jochen Schulz's avatar
Jochen Schulz committed
327
```{code-block}
328
329
330
331
array_object.reshape(newshape)
```
Die `reshape`-Funktion erlaubt es, die `shape` eines Arrays zu ändern, wobei die Daten beibehalten werden.

Jochen Schulz's avatar
Jochen Schulz committed
332
```{code-cell}
333
334
335
336
337
338
339
np.arange(24).reshape((3, 8))
```

Das 2d-Array wird hier *zeilenweise* aus den Werten des `arange`-Arrays aufgebaut.

Eine einzelne Dimension kann auch als `-1` übergeben werden; die entsprechende Größe wird dann berechnet.

Jochen Schulz's avatar
Jochen Schulz committed
340
```{code-cell}
341
342
343
np.arange(24).reshape((2, -1, 3)).shape
```

Jochen Schulz's avatar
Jochen Schulz committed
344
**Array in 1d umwandeln**
345

Jochen Schulz's avatar
Jochen Schulz committed
346
```{code-block}
347
348
349
array_object.ravel()
```

Jochen Schulz's avatar
Jochen Schulz committed
350
```{code-cell}
351
352
353
354
355
np.array([[1, 2], [3, 4]]).ravel()
```

`reshape` und `ravel` erzeugen keine Kopie, es sei denn, es ist unbedingt nötig.

Jochen Schulz's avatar
Jochen Schulz committed
356
**Hinzufügen einer Achse**
357

Jochen Schulz's avatar
Jochen Schulz committed
358
```{code-block}
359
360
361
362
363
364
365
array_object[..., None, ...]
```

Ein häufiger Anwendungsfall von `reshape` ist das Hinzufügen einer Achse mit Länge 1, eine sogenannte *Singleton-Dimension*. Das ist auch möglich über einen "Index-Trick". Durch `None` wird beim Erstellen von Slices im resultierenden Array eine Singleton-Achse hinzugefügt.

*Beispiel*: Konversion eines 1d-Vektors in "Zeilen"- oder "Spalten"-Vektor:

Jochen Schulz's avatar
Jochen Schulz committed
366
```{code-cell}
367
368
369
370
371
372
a = np.arange(3)
b = a[None, :]
print(b.shape)
b
```

Jochen Schulz's avatar
Jochen Schulz committed
373
```{code-cell}
374
375
376
377
378
b = a[:, None]
print(b.shape)
b
```

Jochen Schulz's avatar
Jochen Schulz committed
379
**Array transponieren**
380

Jochen Schulz's avatar
Jochen Schulz committed
381
```{code-block}
382
383
384
385
386
387
array_object.T
array_object.transpose()
```

`transpose` ändert die Reihenfolge der Dimensionen:

Jochen Schulz's avatar
Jochen Schulz committed
388
```{code-cell}
389
390
391
392
393
np.arange(24).reshape((3, 8)).transpose()
```

Als Abkürzung geht auch `.T`:

Jochen Schulz's avatar
Jochen Schulz committed
394
```{code-cell}
395
396
397
398
399
np.arange(24).reshape((3, 8)).T
```

Bei mehrdimensionalen Arrays kehrt `transpose` die Reihenfolge der Dimensionen um.

Jochen Schulz's avatar
Jochen Schulz committed
400
```{code-cell}
401
402
403
404
405
406
a = np.arange(24).reshape((2, 3, 4))
a.transpose().shape
```

Es können auch nur einige der Dimensionen vertauscht werden, indem die neue Reihenfolge der Dimensionen an `transpose` übergeben wird.

Jochen Schulz's avatar
Jochen Schulz committed
407
```{code-cell}
408
409
410
a.transpose((0, 2, 1)).shape
```

Nils Beyer's avatar
Nils Beyer committed
411
(np_elementar)=
412
413
414
## Elementare Operationen
Die meisten Operationen werden elementweise durchgeführt. Man nennt die Funktionen, die das tun, auch *universal functions* (ufunc).

Jochen Schulz's avatar
Jochen Schulz committed
415
```{code-cell}
416
417
418
419
420
421
np.sin(np.arange(5))
[np.sin(t) for t in np.arange(5)]
```

Für binäre Operationen, also `+`, `*`, `==` etc.,  müssen die Arrays i.A. die gleiche `shape` haben (siehe allerdings auch *Broadcasting* weiter unten).

Jochen Schulz's avatar
Jochen Schulz committed
422
```{code-cell}
423
424
425
426
427
np.arange(0, 3) + np.arange(10, 13)
```

Alternativ kann ein Operand auch skalar sein.

Jochen Schulz's avatar
Jochen Schulz committed
428
```{code-cell}
429
430
431
432
433
10 * np.arange(3)
```

Vergleichs-Operationen liefern Arrays aus Bool-Werten als Ergebnis.

Jochen Schulz's avatar
Jochen Schulz committed
434
```{code-cell}
435
436
437
438
439
np.array([0, 1, 2]) == np.array([0, 2, 4])
```

Zum Testen, ob Arrays gleich sind, sollte `np.array_equal` verwendet werden. Wegen Rundungsfehlern ist das im numerischen Kontext aber selten sinnvoll.

Jochen Schulz's avatar
Jochen Schulz committed
440
```{code-cell}
441
442
443
444
445
446
447
np.array_equal(np.array([0, 1, 2]), np.array([0, 1, 3]))
```

## Reduktionen

Reduktionen sind Operationen, bei denen die Werte entlang einer oder mehrerer Achsen des Arrays kombiniert werden. Die meisten Reduktionen verringern dabei die Dimension des Arrays.

Jochen Schulz's avatar
Jochen Schulz committed
448
**Summe, Produkt, etc.**
449

Jochen Schulz's avatar
Jochen Schulz committed
450
```{code-block}
451
452
453
454
455
456
457
458
459
460
461
462
array_object.sum(axis=None)   # Summe
array_object.prod(axis=None)  # Produkt
array_object.max(axis=None)   # Maximum
array_object.min(axis=None)   # Minimum
array_object.mean(axis=None)  # Mittelwert
```

berechnen die jeweilige Reduktion der Elemente des Arrays entlang einer oder mehrerer Achsen. Das `axis`-Argument hat folgende Möglichkeiten:
- `None` (der Default): Die Summe aller Elemente wird berechnet.
- ein `int`: die Summe wird entlang der entsprechnenden Achse berechnet.
- ein Tupel von `int`s: die Summe wird entlang mehrerer Dimensionen berechnet.

Jochen Schulz's avatar
Jochen Schulz committed
463
```{code-cell}
464
465
466
467
a = np.arange(2*3*4).reshape((2, 3, 4))
a.sum()
```

Jochen Schulz's avatar
Jochen Schulz committed
468
```{code-cell}
469
470
471
a.sum(axis=0)
```

Jochen Schulz's avatar
Jochen Schulz committed
472
```{code-cell}
473
474
475
a.sum(axis=(1, 2))
```

Jochen Schulz's avatar
Jochen Schulz committed
476
**Index von Maximum / Minimum**
477

Jochen Schulz's avatar
Jochen Schulz committed
478
```{code-block}
479
480
481
482
483
484
array_object.argmax(axis=None)
array_object.argmin(axis=None)
```

`argmax` und `argmin` liefern den Index, an dem das Maximum beziehungsweise Minimum angenommen wird.

Jochen Schulz's avatar
Jochen Schulz committed
485
```{code-cell}
486
487
488
489
490
491
492
493
494
np.array([0, 1, 3, 2]).argmax()
```

## Broadcasting

Da fast alle Operationen in Numpy element-weise ausgeführt werden, müssen alle Array-Argumente i.A. die gleiche `shape` haben.

**Broadcasting** schwächt die Bedingung etwas ab: Ist ein Array ein *Singleton* entlang einer Dimension (d.h. hat Länge 1 in dieser Dimension), wird diese *implizit* auf die notwendige Länge dupliziert, falls nötig.

Jochen Schulz's avatar
Jochen Schulz committed
495
[![](https://scipy-lectures.org/_images/numpy_broadcasting.png)](https://scipy-lectures.org/intro/numpy/operations.html#broadcasting)
496
497
498

Broadcasting vergleicht die `shapes` der Arrays **von rechts nach links**. Fehlende Dimensionen und Singletons werden dupliziert, alle anderen müssen übereinstimmen.

Jochen Schulz's avatar
Jochen Schulz committed
499
**Beispiele**
500
501
502
503
504

Es sollen zwei Arrays `a` und `b` addiert werden.

- `b` hat eine *Singleton*-Dimension:

Jochen Schulz's avatar
Jochen Schulz committed
505
    ```{code-block}
506
507
508
509
510
511
512
    a.shape       == (3, 4)
    b.shape       == (1, 4)
    (a + b).shape == (3, 4)
    ```

- `b` hat eine Singleton-Dimension und ist niedriger-dimensional als `a`:

Jochen Schulz's avatar
Jochen Schulz committed
513
    ```{code-block}
514
515
516
517
518
519
520
    a.shape       == (2, 3, 4)
    b.shape       == (   1, 4)
    (a + b).shape == (2, 3, 4)
    ```

- `a` und `b` haben Singleton-Dimensionen, `b` ist niedriger-dimensional:

Jochen Schulz's avatar
Jochen Schulz committed
521
    ```{code-block}
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
    a.shape       == (2, 3, 1)
    b.shape       == (   1, 4)
    (a + b).shape == (2, 3, 4)
    ```


## Gitter/Grid-Erzeugung

Für 2d- und 3d-Plots werden oft Koordinaten-Gitter benötigt.

### Reguläre Gitter

Mit *regulär* ist gemeint, dass die Punkte entlang der Achsen äquidistant sind. Wegen *Broadcasting* genügt es oft, nur die Achsen des Gitters zu erzeugen anstelle des gesamten Gitters, also Arrays, die nur entlang einer Dimension eine Länge größer 1 haben.

#### Achsen-Arrays

Jochen Schulz's avatar
Jochen Schulz committed
538
```{code-block}
539
540
541
542
543
X, Y, ... = np.ogrid[startx:stopx:stepx, starty:stopy:stepy, ...]
```

`ogrid` ist eine Funktion, die ungewöhnlicherweise durch "Indizieren" aufgerufen wird. `start`, `stop` und `step` haben ähnliche Bedeutung wie für Slices, aber *erzeugen* Koordinaten-Achsen, anstatt Teilarrays zu extrahieren.

Jochen Schulz's avatar
Jochen Schulz committed
544
```{code-cell}
545
546
547
548
549
550
551
x, y = np.ogrid[0:6:1, 0:50:10]
print((x.shape, y.shape))
(x, y)
```

Anwendung durch Broadcasting: liefert alle Kombinationen aus Elementen in `x` und `y`:

Jochen Schulz's avatar
Jochen Schulz committed
552
```{code-cell}
553
554
555
556
557
x + y
```

`step` kann auch *imaginär* sein; in dem Fall verhält sich `ogrid` ähnlich wie `linspace`, d.h. die Anzahl der Punkte ist festgelegt, nicht die Schrittweite:

Jochen Schulz's avatar
Jochen Schulz committed
558
```{code-cell}
559
560
np.ogrid[0:13:3j, 0:13:3]
```
Jochen Schulz's avatar
Jochen Schulz committed
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590

**Vorberechnung von vollen Arrays**

Manchmal werden nicht nur die Achsen, sondern volle Koordinaten-Arrays benötigt.
Eine Möglichkeit ist es, das Broadcasting "vorzuberechnen" mit `broadcast_arrays`:

```{code-block}
X, Y, ... = np.broadcast_arrays(x, y, ...)
```

```{code-cell}
x, y = np.ogrid[0:6:1, 0:50:10]
x, y = np.broadcast_arrays(x, y)
print(x)
print(y)
```

Dabei werden, wie beim Broadcasting, keine Kopien gemacht. Schreiben in die Arrays hat also unerwünschte Effekte:

```{code-cell}
x[0, 0] = -1
x
```

Alternativ gibt es `numpy.mgrid`, das wie `ogrid` verwendet wird, aber volle Arrays erzeugt. Diese verbrauchen aber auch entsprechend mehr Speicher und sind allgemein ineffizienter.

```{code-cell}
x, y = np.mgrid[0:6:1, 0:50:10]
print((x.shape, y.shape))
```