NumPy: functie voor gelijktijdige max() en min()

numpy.amax()vindt de maximale waarde in een array, en numpy.amin( )doet hetzelfde voor de min-waarde. Als ik zowel max als min wil vinden, moet ik beide functies aanroepen, wat twee keer over de (zeer grote) array moet gaan, wat traag lijkt.

Is er een functie in de numpy API die zowel max als min vindt met slechts een enkele doorgang door de gegevens?


Antwoord 1, autoriteit 100%

Is er een functie in de numpy API die zowel max als min vindt met slechts een enkele doorgang door de gegevens?

Nee. Op het moment van schrijven is er geen dergelijke functie. (En ja, als er waszo’n functie, dan zouden de prestaties aanzienlijkbeter zijn dan het aanroepen van numpy.amin()en numpy.amax()achtereenvolgens op een grote array.)


Antwoord 2, autoriteit 56%

Ik denk niet dat het twee keer passeren van de array een probleem is.Overweeg de volgende pseudo-code:

minval = array[0]
maxval = array[0]
for i in array:
    if i < minval:
       minval = i
    if i > maxval:
       maxval = i

Hoewel er hier maar 1 lus is, zijn er nog steeds 2 controles. (In plaats van 2 lussen met elk 1 vinkje). Echt het enige dat je bespaart is de overhead van 1 lus. Als de arrays echt groot zijn, zoals je zegt, is die overhead klein in vergelijking met de werkelijke werkbelasting van de lus. (Merk op dat dit allemaal in C is geïmplementeerd, dus de lussen zijn hoe dan ook min of meer vrij).


BEWERKENSorry voor jullie vier die upvoten en vertrouwen in me hadden. Je kunt dit zeker optimaliseren.

Hier is wat fortran-code die via f2pyin een python-module kan worden gecompileerd (misschien kan een Cython-goeroe langskomen en dit vergelijken met een geoptimaliseerde C-versie … ):

subroutine minmax1(a,n,amin,amax)
  implicit none
  !f2py intent(hidden) :: n
  !f2py intent(out) :: amin,amax
  !f2py intent(in) :: a
  integer n
  real a(n),amin,amax
  integer i
  amin = a(1)
  amax = a(1)
  do i=2, n
     if(a(i) > amax)then
        amax = a(i)
     elseif(a(i) < amin) then
        amin = a(i)
     endif
  enddo
end subroutine minmax1
subroutine minmax2(a,n,amin,amax)
  implicit none
  !f2py intent(hidden) :: n
  !f2py intent(out) :: amin,amax
  !f2py intent(in) :: a
  integer n
  real a(n),amin,amax
  amin = minval(a)
  amax = maxval(a)
end subroutine minmax2

Compileer het via:

f2py -m untitled -c fortran_code.f90

En nu zijn we op een plek waar we het kunnen testen:

import timeit
size = 100000
repeat = 10000
print timeit.timeit(
    'np.min(a); np.max(a)',
    setup='import numpy as np; a = np.arange(%d, dtype=np.float32)' % size,
    number=repeat), " # numpy min/max"
print timeit.timeit(
    'untitled.minmax1(a)',
    setup='import numpy as np; import untitled; a = np.arange(%d, dtype=np.float32)' % size,
    number=repeat), '# minmax1'
print timeit.timeit(
    'untitled.minmax2(a)',
    setup='import numpy as np; import untitled; a = np.arange(%d, dtype=np.float32)' % size,
    number=repeat), '# minmax2'

De resultaten zijn een beetje onthutsend voor mij:

8.61869883537 # numpy min/max
1.60417699814 # minmax1
2.30169081688 # minmax2

Ik moet zeggen dat ik het niet helemaal begrijp. Alleen np.minvergelijken met minmax1en minmax2is nog steeds een verloren strijd, dus het is niet alleen een geheugenprobleem …

notes— Grootte vergroten met een factor 10**aen herhaling verminderen met een factor 10**a(waarbij de probleemgrootte constant) verandert de prestaties, maar niet op een schijnbaar consistente manier, wat aantoont dat er een wisselwerking is tussen geheugenprestaties en de overhead van de functieaanroep in python. Zelfs het vergelijken van een eenvoudige minimplementatie in fortran verslaat numpy’s met een factor van ongeveer 2 …


Antwoord 3, autoriteit 49%

Je zou Numbakunnen gebruiken, wat een NumPy-bewuste dynamische Python-compiler is die LLVM gebruikt. De resulterende implementatie is vrij eenvoudig en duidelijk:

import numpy
import numba
@numba.jit
def minmax(x):
    maximum = x[0]
    minimum = x[0]
    for i in x[1:]:
        if i > maximum:
            maximum = i
        elif i < minimum:
            minimum = i
    return (minimum, maximum)
numpy.random.seed(1)
x = numpy.random.rand(1000000)
print(minmax(x) == (x.min(), x.max()))

Het moet ook sneller zijn dan de min() & max()implementatie. En dat allemaal zonder een enkele C/Fortran-regel code te hoeven schrijven.

Doe uw eigen prestatietests, aangezien dit altijd afhankelijk is van uw architectuur, uw gegevens, uw pakketversies…


Antwoord 4, autoriteit 44%

Er is een functie voor het vinden (max-min) genaamd numpy.ptpals dat nuttig voor je is:

>>> import numpy
>>> x = numpy.array([1,2,3,4,5,6])
>>> x.ptp()
5

maar ik denk niet dat er een manier is om zowel min als max te vinden met één verplaatsing.

BEWERKEN:ptp roept gewoon min en max onder de motorkap


Antwoord 5, autoriteit 29%

Gewoon om wat ideeën op te doen over de cijfers die je zou kunnen verwachten, gezien de volgende benaderingen:

import numpy as np
def extrema_np(arr):
    return np.max(arr), np.min(arr)
import numba as nb
@nb.jit(nopython=True)
def extrema_loop_nb(arr):
    n = arr.size
    max_val = min_val = arr[0]
    for i in range(1, n):
        item = arr[i]
        if item > max_val:
            max_val = item
        elif item < min_val:
            min_val = item
    return max_val, min_val
import numba as nb
@nb.jit(nopython=True)
def extrema_while_nb(arr):
    n = arr.size
    odd = n % 2
    if not odd:
        n -= 1
    max_val = min_val = arr[0]
    i = 1
    while i < n:
        x = arr[i]
        y = arr[i + 1]
        if x > y:
            x, y = y, x
        min_val = min(x, min_val)
        max_val = max(y, max_val)
        i += 2
    if not odd:
        x = arr[n]
        min_val = min(x, min_val)
        max_val = max(x, max_val)
    return max_val, min_val
%%cython -c-O3 -c-march=native -a
#cython: language_level=3, boundscheck=False, wraparound=False, initializedcheck=False, cdivision=True, infer_types=True
import numpy as np
cdef void _extrema_loop_cy(
        long[:] arr,
        size_t n,
        long[:] result):
    cdef size_t i
    cdef long item, max_val, min_val
    max_val = arr[0]
    min_val = arr[0]
    for i in range(1, n):
        item = arr[i]
        if item > max_val:
            max_val = item
        elif item < min_val:
            min_val = item
    result[0] = max_val
    result[1] = min_val
def extrema_loop_cy(arr):
    result = np.zeros(2, dtype=arr.dtype)
    _extrema_loop_cy(arr, arr.size, result)
    return result[0], result[1]
%%cython -c-O3 -c-march=native -a
#cython: language_level=3, boundscheck=False, wraparound=False, initializedcheck=False, cdivision=True, infer_types=True
import numpy as np
cdef void _extrema_while_cy(
        long[:] arr,
        size_t n,
        long[:] result):
    cdef size_t i, odd
    cdef long x, y, max_val, min_val
    max_val = arr[0]
    min_val = arr[0]
    odd = n % 2
    if not odd:
        n -= 1
    max_val = min_val = arr[0]
    i = 1
    while i < n:
        x = arr[i]
        y = arr[i + 1]
        if x > y:
            x, y = y, x
        min_val = min(x, min_val)
        max_val = max(y, max_val)
        i += 2
    if not odd:
        x = arr[n]
        min_val = min(x, min_val)
        max_val = max(x, max_val)
    result[0] = max_val
    result[1] = min_val
def extrema_while_cy(arr):
    result = np.zeros(2, dtype=arr.dtype)
    _extrema_while_cy(arr, arr.size, result)
    return result[0], result[1]

(de extrema_loop_*()benaderingen zijn vergelijkbaar met wat hierwordt voorgesteld, terwijl extrema_while_*()benaderingen zijn gebaseerd op de code van hier)

De volgende tijden:

geef aan dat de extrema_while_*()de snelste zijn, waarbij extrema_while_nb()de snelste is. In ieder geval presteren ook de extrema_loop_nb()en extrema_loop_cy()oplossingen beter dan de NumPy-only benadering (met behulp van np.max()en np.min()afzonderlijk).

Ten slotte, merk op dat geen van deze zo flexibel is als np.min()/np.max()(in termen van n-dim ondersteuning, axisparameter, enz.).

(volledige code is hier)


Antwoord 6, autoriteit 21%

Over het algemeen kunt u het aantal vergelijkingen voor een minmax-algoritme verminderen door twee elementen tegelijk te verwerken en alleen de kleinere te vergelijken met het tijdelijke minimum en de grotere met het tijdelijke maximum. Gemiddeld heb je maar 3/4 van de vergelijkingen nodig dan een naïeve benadering.

Dit kan worden geïmplementeerd in c of fortran (of een andere taal op laag niveau) en zou qua prestaties bijna onverslaanbaar moeten zijn. Ik gebruik numbaom het principe te illustreren en een zeer snelle, dtype-onafhankelijke implementatie:

import numba as nb
import numpy as np
@nb.njit
def minmax(array):
    # Ravel the array and return early if it's empty
    array = array.ravel()
    length = array.size
    if not length:
        return
    # We want to process two elements at once so we need
    # an even sized array, but we preprocess the first and
    # start with the second element, so we want it "odd"
    odd = length % 2
    if not odd:
        length -= 1
    # Initialize min and max with the first item
    minimum = maximum = array[0]
    i = 1
    while i < length:
        # Get the next two items and swap them if necessary
        x = array[i]
        y = array[i+1]
        if x > y:
            x, y = y, x
        # Compare the min with the smaller one and the max
        # with the bigger one
        minimum = min(x, minimum)
        maximum = max(y, maximum)
        i += 2
    # If we had an even sized array we need to compare the
    # one remaining item too.
    if not odd:
        x = array[length]
        minimum = min(x, minimum)
        maximum = max(x, maximum)
    return minimum, maximum

Het is beslist sneller dan de naïeve benadering die Pequepresenteerde:

arr = np.random.random(3000000)
assert minmax(arr) == minmax_peque(arr)  # warmup and making sure they are identical 
%timeit minmax(arr)            # 100 loops, best of 3: 2.1 ms per loop
%timeit minmax_peque(arr)      # 100 loops, best of 3: 2.75 ms per loop

Zoals verwacht duurt de nieuwe minmax-implementatie slechts ongeveer 3/4 van de tijd die de naïeve implementatie in beslag nam (2.1 / 2.75 = 0.7636363636363637)


Antwoord 7, autoriteit 19%

Niemand noemde numpy.percentile, dus ik dacht dat ik dat zou doen. Als je om [0, 100]percentielen vraagt, krijg je een array van twee elementen, de min (0e percentiel) en de max (100e percentiel).

Het voldoet echter niet aan het doel van de OP: het is niet sneller dan min en max afzonderlijk. Dat komt waarschijnlijk door een aantal machines die niet-extreme percentielen mogelijk maken (een moeilijker probleem, dat zoulanger zou moeten duren).

In [1]: import numpy
In [2]: a = numpy.random.normal(0, 1, 1000000)
In [3]: %%timeit
   ...: lo, hi = numpy.amin(a), numpy.amax(a)
   ...: 
100 loops, best of 3: 4.08 ms per loop
In [4]: %%timeit
   ...: lo, hi = numpy.percentile(a, [0, 100])
   ...: 
100 loops, best of 3: 17.2 ms per loop
In [5]: numpy.__version__
Out[5]: '1.14.4'

Een toekomstige versie van Numpy zou in een speciaal geval kunnen voorzien om de normale percentielberekening over te slaan als alleen [0, 100]wordt gevraagd. Zonder iets aan de interface toe te voegen, is er een manier om Numpy om min en max in één oproep te vragen (in tegenstelling tot wat werd gezegd in het geaccepteerde antwoord), maar de standaardimplementatie van de bibliotheek maakt geen gebruik van deze zaak om het te maken de moeite waard.


Antwoord 8, autoriteit 14%

Dit is een oude thread, maar goed, als iemand hier ooit nog eens naar kijkt…

Als je tegelijkertijd zoekt naar de min en max, is het mogelijk om het aantal vergelijkingen te verminderen. Als het floats zijn die je vergelijkt (wat ik denk dat het is), kan dit je wat tijd besparen, hoewel het geen computationele complexiteit is.

In plaats van (Python-code):

_max = ar[0]
_min=  ar[0]
for ii in xrange(len(ar)):
    if _max > ar[ii]: _max = ar[ii]
    if _min < ar[ii]: _min = ar[ii]

je kunt eerst twee aangrenzende waarden in de array vergelijken, en dan alleen de kleinere vergelijken met het huidige minimum, en de grotere met het huidige maximum:

## for an even-sized array
_max = ar[0]
_min = ar[0]
for ii in xrange(0, len(ar), 2)):  ## iterate over every other value in the array
    f1 = ar[ii]
    f2 = ar[ii+1]
    if (f1 < f2):
        if f1 < _min: _min = f1
        if f2 > _max: _max = f2
    else:
        if f2 < _min: _min = f2
        if f1 > _max: _max = f1

De code hier is geschreven in Python, duidelijk voor snelheid zou je C of Fortran of Cython gebruiken, maar op deze manier doe je 3 vergelijkingen per iteratie, met len(ar)/2 iteraties, wat 3/2 * len(ar geeft) ) vergelijkingen. In tegenstelling daarmee doe je de vergelijking “op de voor de hand liggende manier” twee vergelijkingen per iteratie, wat leidt tot 2*len(ar) vergelijkingen. Bespaart u 25% van de vergelijkingstijd.

Misschien zal iemand dit ooit nuttig vinden.


Antwoord 9, autoriteit 10%

Op het eerste gezicht numpy.histogramverschijntom de slag te slaan:

count, (amin, amax) = numpy.histogram(a, bins=1)

… maar als je kijkt naar de sourcevoor die functie, roept het eenvoudigweg onafhankelijk a.min()en a.max()aan, en vermijdt daarom de prestatieproblemen die in deze vraag. 🙁

Evenzo, scipy.ndimage.measurements.extremaziet eruit als een mogelijkheid, maar ook, noemt u gewoon a.min()EN a.max()onafhankelijk.


Antwoord 10, Autoriteit 5%

Het was sowieso de moeite waard, dus ik zal hier de moeilijkste en minst elegante oplossing voorstellen voor wie er ook geïnteresseerd is. Mijn oplossing is om een ​​MIN-MAX-MAX in één Pass-algoritme in C++ te implementeren en deze te gebruiken om een ​​Python-uitbreidingsmodule te maken. Deze inspanning vereist een beetje overhead om te leren hoe het Python en Numpy C / C++ API’s te gebruiken, en hier zal ik de code tonen en een aantal kleine uitleg en referenties geven voor degene die dit pad wenst.

Mini-threaded min / max

Er is hier niets te interessants. De array wordt opgesplitst in brokken van grootte length / workers. De MIN / MAX wordt berekend voor elk stuk in een future, die vervolgens worden gescand voor de Global Min / Max.

   // mt_np.cc
    //
    // multi-threaded min/max algorithm
    #include <algorithm>
    #include <future>
    #include <vector>
    namespace mt_np {
    /*
     * Get {min,max} in interval [begin,end)
     */
    template <typename T> std::pair<T, T> min_max(T *begin, T *end) {
      T min{*begin};
      T max{*begin};
      while (++begin < end) {
        if (*begin < min) {
          min = *begin;
          continue;
        } else if (*begin > max) {
          max = *begin;
        }
      }
      return {min, max};
    }
    /*
     * get {min,max} in interval [begin,end) using #workers for concurrency
     */
    template <typename T>
    std::pair<T, T> min_max_mt(T *begin, T *end, int workers) {
      const long int chunk_size = std::max((end - begin) / workers, 1l);
      std::vector<std::future<std::pair<T, T>>> min_maxes;
      // fire up the workers
      while (begin < end) {
        T *next = std::min(end, begin + chunk_size);
        min_maxes.push_back(std::async(min_max<T>, begin, next));
        begin = next;
      }
      // retrieve the results
      auto min_max_it = min_maxes.begin();
      auto v{min_max_it->get()};
      T min{v.first};
      T max{v.second};
      while (++min_max_it != min_maxes.end()) {
        v = min_max_it->get();
        min = std::min(min, v.first);
        max = std::max(max, v.second);
      }
      return {min, max};
    }
    }; // namespace mt_np

De Python Extension-module

Hier beginnen dingen ugly te krijgen … Een manier om C++ -code in Python te gebruiken, is om een ​​uitbreidingsmodule te implementeren. Deze module kan worden gebouwd en geïnstalleerd met behulp van de distutils.corestandaardmodule. Een volledige beschrijving van wat dit met zich meebrengt, is bedekt met de Python-documentatie: https: // docs. python.org/3/extenending/extenend.html . Opmerking: Er zijn zeker andere manieren om vergelijkbare resultaten te krijgen, om https://docs.python.org/3/extenending/index.html#extending-index :

Deze handleiding heeft alleen betrekking op de basishulpmiddelen voor het maken van verlengingen die zijn aangeboden als onderdeel van deze versie van CPYTHON. Tools van derden zoals Cython, Cffi, Swig en Numba bieden zowel eenvoudiger als meer geavanceerde benaderingen voor het maken van C- en C++ -extensies voor Python.

In wezen is deze route waarschijnlijk academisch dan praktisch. Met dat gezegd hebbende, wat ik vervolgens deed was, steekt hij vrij dicht bij de tutorial, maak een module-bestand aan. Dit is in wezen boilerplaat voor onderscheidingen om te weten wat te doen met uw code en er een pythonmodule uit te maken. Voordat het een van dit doet, is het waarschijnlijk verstandig om een ​​python virtuele omgeving te maken , zodat u uw systeempakketten niet vervuilt (zie https://docs.python.org/3/Library/venv.html#module-venv ).

Hier is het module-bestand:

// mt_np_forpy.cc
//
// C++ module implementation for multi-threaded min/max for np
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#include <python3.6/numpy/arrayobject.h>
#include "mt_np.h"
#include <cstdint>
#include <iostream>
using namespace std;
/*
 * check:
 *  shape
 *  stride
 *  data_type
 *  byteorder
 *  alignment
 */
static bool check_array(PyArrayObject *arr) {
  if (PyArray_NDIM(arr) != 1) {
    PyErr_SetString(PyExc_RuntimeError, "Wrong shape, require (1,n)");
    return false;
  }
  if (PyArray_STRIDES(arr)[0] != 8) {
    PyErr_SetString(PyExc_RuntimeError, "Expected stride of 8");
    return false;
  }
  PyArray_Descr *descr = PyArray_DESCR(arr);
  if (descr->type != NPY_LONGLTR && descr->type != NPY_DOUBLELTR) {
    PyErr_SetString(PyExc_RuntimeError, "Wrong type, require l or d");
    return false;
  }
  if (descr->byteorder != '=') {
    PyErr_SetString(PyExc_RuntimeError, "Expected native byteorder");
    return false;
  }
  if (descr->alignment != 8) {
    cerr << "alignment: " << descr->alignment << endl;
    PyErr_SetString(PyExc_RuntimeError, "Require proper alignement");
    return false;
  }
  return true;
}
template <typename T>
static PyObject *mt_np_minmax_dispatch(PyArrayObject *arr) {
  npy_intp size = PyArray_SHAPE(arr)[0];
  T *begin = (T *)PyArray_DATA(arr);
  auto minmax =
      mt_np::min_max_mt(begin, begin + size, thread::hardware_concurrency());
  return Py_BuildValue("(L,L)", minmax.first, minmax.second);
}
static PyObject *mt_np_minmax(PyObject *self, PyObject *args) {
  PyArrayObject *arr;
  if (!PyArg_ParseTuple(args, "O", &arr))
    return NULL;
  if (!check_array(arr))
    return NULL;
  switch (PyArray_DESCR(arr)->type) {
  case NPY_LONGLTR: {
    return mt_np_minmax_dispatch<int64_t>(arr);
  } break;
  case NPY_DOUBLELTR: {
    return mt_np_minmax_dispatch<double>(arr);
  } break;
  default: {
    PyErr_SetString(PyExc_RuntimeError, "Unknown error");
    return NULL;
  }
  }
}
static PyObject *get_concurrency(PyObject *self, PyObject *args) {
  return Py_BuildValue("I", thread::hardware_concurrency());
}
static PyMethodDef mt_np_Methods[] = {
    {"mt_np_minmax", mt_np_minmax, METH_VARARGS, "multi-threaded np min/max"},
    {"get_concurrency", get_concurrency, METH_VARARGS,
     "retrieve thread::hardware_concurrency()"},
    {NULL, NULL, 0, NULL} /* sentinel */
};
static struct PyModuleDef mt_np_module = {PyModuleDef_HEAD_INIT, "mt_np", NULL,
                                          -1, mt_np_Methods};
PyMODINIT_FUNC PyInit_mt_np() { return PyModule_Create(&mt_np_module); }

In dit bestand is er een aanzienlijk gebruik van de Python en de Numpy API, voor meer informatie Consult: https://docs.python.org/3/c-api/arg.html#c.pyarg_paretuple , en voor numpy: https://docs.scipy.org/doc/numpy/reference/c-api. array.html .

De module installeren

Het volgende ding is om onderscheid te maken met Distutils om de module te installeren. Dit vereist een setup-bestand:

# setup.py
from distutils.core import setup,Extension
module = Extension('mt_np', sources = ['mt_np_module.cc'])
setup (name = 'mt_np', 
       version = '1.0', 
       description = 'multi-threaded min/max for np arrays',
       ext_modules = [module])

Als u de module uiteindelijk wilt installeren, voert u python3 setup.py installuit vanuit uw virtuele omgeving.

De module testen

Ten slotte kunnen we testen of de C++-implementatie daadwerkelijk beter presteert dan het naïeve gebruik van NumPy. Hier is een eenvoudig testscript om dit te doen:

# timing.py
# compare numpy min/max vs multi-threaded min/max
import numpy as np
import mt_np
import timeit
def normal_min_max(X):
  return (np.min(X),np.max(X))
print(mt_np.get_concurrency())
for ssize in np.logspace(3,8,6):
  size = int(ssize)
  print('********************')
  print('sample size:', size)
  print('********************')
  samples = np.random.normal(0,50,(2,size))
  for sample in samples:
    print('np:', timeit.timeit('normal_min_max(sample)',
                 globals=globals(),number=10))
    print('mt:', timeit.timeit('mt_np.mt_np_minmax(sample)',
                 globals=globals(),number=10))

Dit zijn de resultaten die ik heb gekregen door dit alles te doen:

8  
********************  
sample size: 1000  
********************  
np: 0.00012079699808964506  
mt: 0.002468645994667895  
np: 0.00011947099847020581  
mt: 0.0020772050047526136  
********************  
sample size: 10000  
********************  
np: 0.00024697799381101504  
mt: 0.002037393998762127  
np: 0.0002713389985729009  
mt: 0.0020942929986631498  
********************  
sample size: 100000  
********************  
np: 0.0007130410012905486  
mt: 0.0019842900001094677  
np: 0.0007540129954577424  
mt: 0.0029724110063398257  
********************  
sample size: 1000000  
********************  
np: 0.0094779249993735  
mt: 0.007134920000680722  
np: 0.009129883001151029  
mt: 0.012836456997320056  
********************  
sample size: 10000000  
********************  
np: 0.09471094200125663  
mt: 0.0453535050037317  
np: 0.09436299200024223  
mt: 0.04188535599678289  
********************  
sample size: 100000000  
********************  
np: 0.9537652180006262  
mt: 0.3957935369980987  
np: 0.9624398809974082  
mt: 0.4019058070043684  

Deze zijn veel minder bemoedigend dan de resultaten eerder in de thread aangeven, die ergens rond de 3,5x versnelling aangaven, en geen multi-threading bevatten. De resultaten die ik heb bereikt zijn redelijk, ik zou verwachten dat de overhead van threading de tijd zou domineren totdat de arrays erg groot werden, op welk punt de prestatieverbetering std::thread::hardware_concurrencyx verhogen.

Conclusie

Er is zeker ruimte voor toepassingsspecifieke optimalisaties van sommige NumPy-code, zo lijkt het, met name met betrekking tot multi-threading. Of het de moeite waard is, is mij niet duidelijk, maar het lijkt me zeker een goede oefening (of zoiets). Ik denk dat het misschien beter is om tijd te besteden aan het leren van een aantal van die ‘tools van derden’, zoals Cython, maar wie weet.


Antwoord 11, autoriteit 3%

Geïnspireerd door het vorige antwoordheb ik de numba-implementatie geschreven waarbij minmax voor axis=0 uit 2D-array wordt geretourneerd. Het is ~5x sneller dan numpy min/max bellen.
Misschien vindt iemand het nuttig.

from numba import jit
@jit
def minmax(x):
    """Return minimum and maximum from 2D array for axis=0."""    
    m, n = len(x), len(x[0])
    mi, ma = np.empty(n), np.empty(n)
    mi[:] = ma[:] = x[0]
    for i in range(1, m):
        for j in range(n):
            if x[i, j]>ma[j]: ma[j] = x[i, j]
            elif x[i, j]<mi[j]: mi[j] = x[i, j]
    return mi, ma
x = np.random.normal(size=(256, 11))
mi, ma = minmax(x)
np.all(mi == x.min(axis=0)), np.all(ma == x.max(axis=0))
# (True, True)
%timeit x.min(axis=0), x.max(axis=0) 
# 15.9 µs ± 9.4 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%timeit minmax(x) 
# 2.62 µs ± 31.3 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

Antwoord 12

De kortste manier die ik heb bedacht is deze:

mn, mx = np.sort(ar)[[0, -1]]

Maar aangezien het de array sorteert, is het niet de meest efficiënte.

Een andere korte manier zou zijn:

mn, mx = np.percentile(ar, [0, 100])

Dit zou efficiënter moeten zijn, maar het resultaat wordt berekend en er wordt een float geretourneerd.

Other episodes