<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xml" href="/cjs/screen.xsl" media="screen"?>
<lecture>

<meta>
  <maintitle>Python</maintitle>
  <author>Jiří Znamenáček</author>
  <title>NumPy</title>
  <date>2016-07-26</date>
  <link><!--a href="http://vyuka.ookami.cz" rel="external">http://vyuka.ookami.cz</a--></link>
  <!--
  <style>
    /* CSSka */
  </style>
  <use-math/>
  -->
</meta>
<!--
  „“–…
  ←→ ↑↓ ↔↕
  ↵ aneb &#x21B5; aneb \r aneb CR aneb CarriageReturn
-->


<slide title="Úvod">

    <p>
        Python původně asi vůbec neměl ambice stát se <em>linguou francou</em> vědecké komunity, nicméně už od počátku spoustu lidí zaujal, a tak asi není divu, že již v roce 1995 začala skupina zájemců pracovat na podpoře pro maticové výpočty.
    </p>
    <p>
        Byť výsledek jejich práce se nikdy součástí oficiální distribuce Python'u nestal, asi nejen díky zapojení Guida van Rossuma v začátku práce na tomto balíku do Python'u postupně probublala podpora pro jeho snadné nasazení (především v podobě <a href="slices.xml">rozšířené výřezové notace</a>).
    </p>
    <p>
        Chcete-li dnes v Python'u používat vícerozměrná pole (a to ne nutně pouze číselná), je instalace knihovny <a href="http://numpy.org" class="external"><em>NumPy</em></a> víceméně automatickou volbou.
    </p>
    <note>
        Nutno podotknout, že <em>NumPy</em> není všemocný a neustále probíhá i vývoj jeho upravených nebo jím inspirovaných verzí – kupříkladu balíku <a href="http://blaze.readthedocs.io" class="external"><em>Blaze</em></a> pro práci s „opravdu velikými daty“ (která se nevejdou do paměti).
    </note>

</slide>
<slide title="Poznámka k NumPy obecně">

    <p class="enumerate">
        Hned na začátek této úvodní přednášky bohužel jedno důležité upozornění:
    </p>
    <blockquote>
        Narozdíl od Python'u <strong>neprošel NumPy žádným „vyčištěním starého balastu“</strong>, takže podle toho po (kumulovaných) dvaceti letech vývoje bohužel vypadá…
    </blockquote>
    <p>
        Není proto výjimkou – spíše pravidlem :-( – mít na stejnou funkcionalitu více různých metod, a to dokonce i ve stejném modulu. Někdy jsou úplně stejné, jindy dávají stejné výsledky jen pro trochu jiné volby parametrů a podobně. Kolega to vyjádřil lapidárně: „NumPy vypadá, jako kdyby do něj každý dopsal to, co zrovna potřeboval, aniž by se předtím pořádně podíval, jestli v něm náhodou už něco podobného není.“
    </p>
    <note>
        Například <em>numpy.random.random()</em> a <em>numpy.random.ranf()</em> dělají obě nachlup úplně to samé (a také mají do posledního písmenka úplně stejnou dokumentaci). Ale takové <em>numpy.random.random_integers()</em> – asi naštěstí od poslední verze určené k dalšímu nepoužívání (byť dokumentace k tomu není ani trochu konzistentní) – se opravdu od <em>numpy.random.randint()</em> liší dosti neuvěřitelným způsobem.
    </note>
    <p>
        V podstatě bych řekl, že <em>NumPy</em> vypadá tak trochu jako Python řady 2.x, možná dokonce ještě o něco hůř. Jelikož nemá moc použitelných alternativ, musíme s tím přežít.
    </p>

    <p class="enumerate">
        Druhou důležitou poznámkou je, že Numpy je vyvíjen s plánovaným <strong>odstavováním starší funkcionality verzi od verze</strong>. Což by nevadilo, pokud by se při tom kód postupně nestával zpětně nekompatibilní. Anebo ani to by možná tak nevadilo, kdybyste vždy pohledem do kódu dokázali z příkazu <code>import numpy</code> vydedukovat, jakou verzi měl zrovna autor na mysli. Což ale samozřejmě nejde…
    </p>
    <note>
        Celá situace tak trochu připomíná rozdíly mezi trojkovým a dvojkovým Python'em, kdy vás každý (s trochou štěstí) upozorní, jakou verzi měl pro daný skript na mysli (a i bez toho by to šlo relativně snadno poznat). Bohužel pro Numpy to neplatí – přijít na to, pro kterou verzi byl daný skript napsán, pokud to nějaká dobrá duše neuvedla jako komentář v kódu, je slušná detektivní práce, a to i pokud si v hlavě nosíte kompletní seznam změn v průběhu historie vývoje…
    </note>

    <p class="enumerate">
        A do třetice všeho zlého: S výše uvedenými dvěma body přímo souvisí další dvě věci, a to <strong>dokumentace a tutoriály</strong>.
    </p>
    <p>
        Že dokumentace vypadá všelijak, ne všechno je dopsané a už vůbec ne zhyperlinkované, že je občas dost těžké přijít na to, která z popisované funkcionality je ta „správná“ pro budoucí použití, je poměrně nešťastné, ale jakž takž pochopitelné. Že ale tutoriály roztroušené po webu jsou prostě napsané pro v té době aktuální verzi <em>Numpy</em> a že si jejich autor s sebou „vláčí“ svoje preference (obzvláště na pytel v případě zlozvyků z Python'u 2.x), je už doslova na zabití.
    </p>
    <note>
        Prakticky to znamená, že hledáte-li, jak se něco dělá, z nejrůznějších zdrojů zjistíte tak čtyři pět různých způsobů, z nichž dva už vám nebudou ani fungovat a u zbývajících budete mít problém zjistit, která volba je ta nejvhodnější.
    </note>

</slide>
<slide title="Vícerozměrná pole">

    <p>
        Základním přínosem knihovny <em>NumPy</em> je <strong>velmi silná podpora vícerozměrných polí, a to včetně nejrůznějších operací nad nimi</strong>.
    </p>
    <p>
        S tím souvisí, že pole je možno zavést mnoha různými způsoby, od klasického pythoního „seznamu seznamů“ (resp. n-tic)..
    </p>
    <example lang="python">
>>> a = numpy.array([ [1, 2, 3], [4, 5, 6] ])
>>> a
array([[1, 2, 3],
       [4, 5, 6]])
    </example>
    <p>
        ..přes nejrůznější zkratky u „klasické“ matice..
    </p>
    <example lang="python">
>>> b = numpy.mat("[1 2 3; 4 5 6]")
>>> b
matrix([[1, 2, 3],
        [4, 5, 6]])
   </example>
    <!--note>
        To samé se ale dá zařídit další armádou variant.
    </note-->
    <p>
        ..až po specifické metody:
    </p>
    <example lang="python">
>>> c = numpy.eye(3)
>>> c
array([[ 1.,  0.,  0.],
       [ 0.,  1.,  0.],
       [ 0.,  0.,  1.]])
    </example>
    <p>
        PS: A to jsem ještě nezmínil třebas maticové pohledy nad daty ze souborového systému a jiné. Nebo to, že prvky polí mohou být i velmi komplikované složené typy a ne jen pouze čísla.
    </p>

</slide>
<slide title="Unární a binární operace">

    <p>
        Unární a <strong>binární operace nad poli jsou v základu prováděny „po složkách“</strong>.
    </p>
    <p>
        Zatímco násobení skalárem je celkem běžné..
    </p>
    <note>
        Máme dáno pole <code>a = numpy.array([[1, 2, 3], [4, 5, 6]])</code> .
    </note>
    <example lang="python">
>>> -a
array([[-1, -2, -3],
       [-4, -5, -6]])

>>> 3 * a
array([[ 3,  6,  9],
       [12, 15, 18]])
    </example>
    <p>
        ..a stejně tak sčítání..
    </p>
    <example lang="python">
>>> a + a
array([[ 2,  4,  6],
       [ 8, 10, 12]])
    </example>
    <p>
        ..už takové násobení při letmé znalosti lineární algebry hlavu zamotá (kdo slyšel o <a href="https://en.wikipedia.org/wiki/Hadamard_product_(matrices)" class="external">Hadamardově součinu</a> matic?):
    </p>
    <example lang="python">
>>> a * a
array([[ 1,  4,  9],
       [16, 25, 36]])
    </example>
    <p>
        <em>NumPy</em> nepřekvapivě obsahuje metody, kterými (většinou) docílíte toho, co zamýšlíte, ale ne vždy jsou tam nebo takové, jak byste možná čekali. (Pokud za sebou nemáte MATLAB, protože spousta věcí je mu prý podobná.)
    </p>

</slide>
<slide title="Speciální metody">

    <p>
        Metody operující nad poli se dají rozdělit přibližně na dvě skupiny:
    </p>
    <ul>
        <li>
            metody aplikující nějakou funkci na všechny prvky pole, např.:
            <example lang="python">
>>> numpy.log([[1, 2, 3], [4, 5, 6]])
array([[ 0.        ,  0.69314718,  1.09861229],
       [ 1.38629436,  1.60943791,  1.79175947]])
            </example>
        </li>
        <li>
            „skutečné“ (nejen) maticové metody, např.:
            <example lang="python">
>>> numpy.array([[1, 2, 3], [4, 5, 6]]).T
array([[1, 4],
       [2, 5],
       [3, 6]])

>>> numpy.linalg.inv([[1, 1], [1, 0]])
array([[ 0.,  1.],
       [ 1., -1.]])
            </example>
        </li>
    </ul>
    <p>
        Ty druhé ale mohou operovat třebas jen po řádcích i jinak, záleží na konkrétní metodě a jejím účelu (a vizi autora kódu).
    </p>

</slide>
<slide title="„Broadcasting“">

    <p>
        Důležitým předpokladem pro správnou práci s knihovnou <em>NumPy</em> je pochopení principu <a href="broadcasting.xml"><strong><em>broadcasting</em></strong>u</a>. Jde v něm o to, že knihovní metody dokážou za jistých – přesně definovaných! – podmínek zpracovat požadavky na operace mezi polemi, jejichž rozměry si z hlediska dané operace jinak nemusí vůbec vyhovovat.
    </p>
    <p>
        Trochu zjednodušeně se dá říct, že pokud je možné jedno (nebo i obě!) pole „protáhnout“ z jedničky na vyhovující rozměr, bude tak (fiktivně) provedeno jeho nakopírováním do chybějících rozměrů a výsledek bude spočítán klasicky složku po složce, jak by se stalo v případě, kdy by obě měla vyhovující rozměr hned na začátku.
    </p>
    <p>
        Spoustu operací dokáže <em>broadcasting</em> velmi elegantně zapsat a díky vnitřní optimalizaci i rychle vyřešit. Pro velmi velké vstupy však už nemusí být rychlejší a vůbec méně náročnější než třebas méně přehledná smyčka nad menšími výseky dat.
    </p>

</slide>
<slide title="Poznámka – typové „konverze“">
    
    <p>
        Jak bylo vidět už na předchozích příkladech, v duchu pythoní tradice dokáží metody <em>Numpy</em> zpracovat vstupy v nejrůznějším provedení.
    </p>
    <p>
        Ještě pohodlnější je, že dokonce i běžné binární operátory dokážou podobným způsobem přizpůsobit jeden ze svých vstupů, pokud ten druhý je numpyovské pole. K poli tak můžete třebas přičítat nejen skalár (tedy pythonovské číslo), ale též i vektor v podobě pythonovského seznamu a podobně:
    </p>
    <example lang="python">
        >>> a = np.arange(3)
        >>> a
        array([0, 1, 2])

        # Operátory si poradí s asociací na obě strany:
        >>> a + [1, 1, 1]
        array([1, 2, 3])
        >>> [1, 1, 1] + a
        array([1, 2, 3])

        # Funguje to i pro jiné rozumné vstupní typy:
        >>> a + (1, 1, 1)
        array([1, 2, 3])
        >>> (1, 1, 1) + a
        array([1, 2, 3])
    </example>

</slide>
<slide title="PS: Dvourozměrná pole versus matice">

    <p>
        Abychom se hned na začátku vyhnuli nedorozumění, <strong>dvourozměrná pole nejsou maticemi v matematickém smyslu slova</strong>! Ovšem knihovna <em>NumPy</em> obsahuje i tyto „matematické matice“, jako podtřídu obecných vícerozměrných polí.
    </p>
    <p>
        Zatímco u vícerozměrných polí všechny běžné matematické (binární) operace fungují po složkách a na maticové operace je třeba používat speciální metody (nebo v případě Python'u 3.5 a výše pro násobení operátor <code>@</code>), ve specifickém případě skutečných <em>matic</em> (tedy specielně označených 2D-polí) fungují „maticově“, tedy tak, jak je člověk zvyklý z lineární algebry:
    </p>
    <example lang="python">
>>> import numpy as np

>>> a = np.array([ [1,2,3], [0,2,3], [0,0,3] ])
>>> b = np.array([ [1,2,3], [4,5,6], [7,8,9] ])
>>> a
array([[1, 2, 3],
       [0, 2, 3],
       [0, 0, 3]])
>>> b
array([[1, 2, 3],
       [4, 5, 6],
       [7, 8, 9]])

>>> am = np.mat(a)
>>> bm = np.mat(b)
>>> am
matrix([[1, 2, 3],
        [0, 2, 3],
        [0, 0, 3]])
>>> bm
matrix([[1, 2, 3],
        [4, 5, 6],
        [7, 8, 9]])

>>> a * b
array([[ 1,  4,  9],
       [ 0, 10, 18],
       [ 0,  0, 27]])
>>> am * bm
matrix([[30, 36, 42],
        [29, 34, 39],
        [21, 24, 27]])
    </example>
    <p>
        Možná nejen proto je používání typu <em>matrix</em> vřele <a href="http://docs.scipy.org/doc/scipy/reference/tutorial/linalg.html#numpy-matrix-vs-2d-numpy-ndarray" class="external">nedoporučováno</a>.
    </p>

</slide>
<slide title="Poznámka k použití pythoního modulu „array“">

    <p>
        Pokud vám zrovna stačí <em>pole jednorozměrné</em> a nepotřebujete <em>žádné dodatečné operace</em> nad jeho prvky, rychlejší a podstatně méně „těžkotonážní“ variantou je modul <a href="/materialy/python/types.plus/arrays.xml"><code>array</code></a>, který je součástí distribuce Python'u.
    </p>
    <p>
        Každý z modulů má i tak ovšem svá specifika, takže si musíte volbu důkladně rozmyslet:
    </p>
    <example lang="python">
>>> import array
>>> ar = array.array('f', [1, 2.0, 3.14])
>>> ar
array('f', [1.0, 2.0, 3.140000104904175])
>>> type(ar)
&lt;class 'array.array'>

>>> import numpy as np
>>> nar = np.array([1, 2.0, 3.14])
>>> nar
array([ 1.  ,  2.  ,  3.14])
>>> type(nar)
&lt;class 'numpy.ndarray'>

>>> for i in range(3):
...   ar[i] == nar[i]
... 
True
True
False
    </example>
    <!--
        http://stackoverflow.com/questions/111983/python-array-versus-numpy-array  
        In defense of array.array, I think its important to note that it is also a lot more lightweight than numpy.array, and that saying 'will do just fine' for a 1D array should really be 'a lot faster, smaller, and works in pypy/cython without issues.' I love NumPy, but for simple arrays the array.array module is actually better. – J.J Nov 1 '15 at 11:59 
    -->

</slide>


</lecture>
