6.11. Redukce

Redukce sbalí pole podél jedné nebo více os pomocí sčítání, průměrování, hledání minima a podobně. Každá redukce je jedním voláním knihovny nad celým polem, což je mnohem rychlejší než ekvivalentní smyčka v Pythonu. numpy pokrývá ty každodenní:

  • sum() – součet všech prvků

  • mean() – aritmetický průměr (součet dělený počtem prvků)

  • std() – směrodatná odchylka, ddof= upravuje dělitele (N - ddof)

  • min() / max() – nejmenší a největší prvek

  • median() – prostřední hodnota po seřazení prvků (50. percentil)

  • argmin() / argmax()index minimálního nebo maximálního prvku

  • all() / any() – redukce pravdivostní hodnoty nad logickými poli

6.11.1. Bez klíčového slova axis

Při volání bez axis= vrací redukce skalár pokrývající celé pole:

a = np.array([1, 2, 3, 4], dtype=np.float)
np.sum(a)           # 10.0
np.mean(a)          # 2.5
np.std(a)           # 1.118...
np.median(a)        # 2.5

b = np.array([40, 10, 30, 20], dtype=np.float)
np.max(b)           # 40.0
np.argmax(b)        # 0  (index of the maximum)

6.11.2. S klíčovým slovem axis

axis= smrští jednu pojmenovanou osu a ostatní ponechá beze změny. Výsledkem je pole o jeden řád nižší než vstup:

m = np.arange(12, dtype=np.float).reshape((3, 4))

np.sum(m)               # 66.0          - scalar
np.sum(m, axis=0)       # length-4      - column sums
np.sum(m, axis=1)       # length-3      - row sums

Stejné pravidlo pro tvar platí pro každou redukci: axis=0 sbalí první osu, axis=1 sbalí druhou a tak dále. Průměr / směrodatná odchylka podél řádku se například zapisují jako np.mean(m, axis=1) a np.std(m, axis=1). Výsledek má délku druhé osy.

Klíčové slovo keepdims=True ponechá smrštěnou osu na místě s délkou 1 místo jejího odstranění. Tento rozdíl je důležitý, když je potřeba redukovaný výsledek vysílat (broadcast) zpět proti původnímu poli: keepdims zachovává řád, což udržuje pravidla broadcastingu zarovnaná osu po ose.

Kanonickým použitím je odečtení průměru každého řádku od tohoto řádku:

m = np.arange(12, dtype=np.float).reshape((3, 4))
row_means = np.mean(m, axis=1, keepdims=True)
# row_means has shape (3, 1)
centred = m - row_means
# (3, 4) - (3, 1) -> (3, 4), each row centred on its own mean

Bez keepdims vrací np.mean(m, axis=1) jednorozměrný výsledek tvaru (3,). Broadcasting (3, 4) - (3,) zarovná (3,) jako (1, 3) po doplnění řádu zleva, což je nekompatibilní s (3, 4): poslední osy si neodpovídají (4 proti 3) a ani jedna není 1, takže numpy vyvolá ValueError. Právě keepdims=True udržuje odečítání platným.

6.11.3. Na uspořádání záleží

Ve spojení s uspořádáním po řádcích (row-major), které popisuje Tvar a kroky, je redukce podél poslední osy nejlevnějším případem. Redukce prochází datový blok ve směru, ve kterém je uložen, bez přeskoků z řádku na řádek:

m = np.arange(2000, dtype=np.float).reshape((2, 1000))
np.sum(m, axis=1)       # cheap - long axis is the inner one
np.sum(m, axis=0)       # has to jump rows on every step

Když má aplikace na výběr, jak rozvrhnout buffer, umístěte dlouhou osu jako poslední, aby redukce podél ní běžely v rychlém směru.

6.11.4. Iterovatelné objekty jako vstup

Většina redukcí přijímá místo objektu ndarray také iterovatelný objekt Pythonu (list, range, n-tici). Toto pohodlí stojí několik mikrosekund za implicitní konverzi – což se ve smyčce rychle nasčítá. Když se stejná data redukují vícekrát, vytvořte ndarray jednou a předávejte jej dál.