{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "## Маски и массивы в Numpy" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Эмпирические данные, полученные в результате измерений, часто имеют такое неприятное свойство как наличие пропусков. Обычно им присваивают значения, которые явно не имеют никакого смысла для этих данных. Так для величин количества осадков или температуры воздуха значения -999 выходят за рамки их естественной изменчивости. Часто пропуски заполняют именно такими значениями типа -999.9.\n", "Однако при расчёте различных статистических параметров (выборочное среднее, выборочная дисперсия и стандартное отклонение) наличие таких значений будет приводить к сильному искажению. Такие значения (их часто называют undefined values) необходимо фильтровать.\n", "Numpy предлагает изящный способ для решения такой задачи - масочные массивы или массивы-маски (masked arrays)." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Описание masked array находится здесь -> \n", "http://docs.scipy.org/doc/numpy/reference/routines.ma.html" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Примеры использования массивов-масок -> \n", "http://docs.scipy.org/doc/numpy/reference/maskedarray.generic.html#data-with-a-given-value-representing-missing-data" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Продемонстрируем возможности этих массивов на следующих примерах." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Пример 1 Фильтрация \"лишних\" значений с помощью обычного массива" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Пусть дана выборка z псевдослучайной величины Z. Она изменяется в пределах [0,1). Искусственно внесём в неё значения, которые на порядок больше характерных значений (здесь прибавлялось 50.0). В реальности это могут быть ошибки наблюдений, ошибки передачи данных, ошибки вычислений и ошибки другого рода. Также это могут быть пропуски в данных." ] }, { "cell_type": "code", "execution_count": 30, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "('Undefined values:', array([ 50.2566024 , 50.01975112, 50.27686163, 50.10802389,\n", " 50.20053467, 50.08686618, 50.23594479, 50.06535428,\n", " 50.26383849, 50.07058549, 50.27683058, 50.29247642,\n", " 50.24023024, 50.29621324, 50.11360462, 50.19390434,\n", " 50.06396334, 50.24955479, 50.10932679, 50.00275559,\n", " 50.02837202, 50.02712106, 50.07807788, 50.28143134,\n", " 50.10331262, 50.12139954, 50.1780714 , 50.15934063,\n", " 50.07576644, 50.19788189, 50.2581496 ]))\n" ] } ], "source": [ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "\n", "NN = 100\n", "z = np.random.random(NN)\n", "\n", "z[z<0.3] = z[z<0.3] + 50.0\n", "ii = np.where(z > 50.0)\n", "print('Undefined values:',z[ii])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Для псевдослучайной величины Z значения больше 1 являются \"лишними\", вбросами/выбросами. Рассчитаем среднее значений и стандартное отклонение без фильтрации." ] }, { "cell_type": "code", "execution_count": 31, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "('Mean=', 16.002037955287626, 'Std=', 22.895577983165108)\n" ] } ], "source": [ "zm1 = np.mean(z)\n", "zs1 = np.std(z)\n", "print('Mean=',zm1,'Std=',zs1)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Получилось, что среднее и стандартное отклонение выборки z совсем не характеризуют изначальную выборку. Даже если выпадающий элемент будет один, но он будет на порядки отличаться от характерных значений (а ещё хуже, если при этом он будет иметь другой знак), то это испортит всю статистику. Такие значений необходимо фильтровать. " ] }, { "cell_type": "code", "execution_count": 40, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "('Filtered mean=', 0.65611084350012427, 'Filtered std=', 0.21865313456304422)\n" ] } ], "source": [ "zm2 = np.mean(z[z<1.0]) # Для функции numpy.mean даётся часть выборки z, где значения меньше единицы. Аналогично с numpy.std.\n", "zs2 = np.std(z[z<1.0])\n", "print('Filtered mean=',zm2,'Filtered std=',zs2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Аналогичный результат даёт другая форма записи кода (в стиле процедурных языков типа Фортран), которая НЕ РЕКОМЕНДУЕТСЯ для python." ] }, { "cell_type": "code", "execution_count": 42, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "('Zmean=', 0.65611084350012427, 'Zstd=', 0.21865313456304444)\n" ] } ], "source": [ "n = 0\n", "zsum = 0.0\n", "zstd = 0.0\n", "for i in range(len(z)):\n", " if(z[i]<1.0): \n", " zsum = zsum + z[i]\n", " zstd = zstd + z[i]*z[i]\n", " n = n + 1\n", "zmean = zsum/n \n", "zstd = zstd/n - zmean**2\n", "zstd = np.sqrt(zstd)\n", "\n", "print('Zmean=',zmean,'Zstd=',zstd)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Пример 2 Фильтрация \"лишних\" значений с помощью масочного массива" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Масочный массив, массив-маска или массив с маской - это особый класс, который отличный от класса numpy.ndarray. С помощью массива-маски предыдущая задача решается так:" ] }, { "cell_type": "code", "execution_count": 51, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "('1way: Masked mean=', 0.65611084350012427, 'Masked std=', 0.21865313456304422)\n", "('2way: Masked mean=', 0.65611084350012427, 'Masked std=', 0.21865313456304422)\n" ] } ], "source": [ "# Первый способ\n", "maska = np.ma.array(z, mask = (z < 1.0), copy = True) # Копируем в массив-маску maska массив z с условием маски\n", "maskm = np.ma.mean(maska)\n", "masks = np.ma.std(maska)\n", "print('1way: Masked mean=',zm2,'Masked std=',zs2)\n", "\n", "# Или так (второй способ)\n", "\n", "zm2 = np.ma.masked_outside(z, 0.0, 1.0).mean() # из выборки z отбираются значения в интервале [0,1] \n", "zs2 = np.ma.masked_outside(z, 0.0, 1.0).std()\n", "print('2way: Masked mean=',zm2,'Masked std=',zs2)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если необходимо считать данные из текстового файла, то можно сразу считывать в массив-маску, а не просто в массив. Такие возможности даёт функция numpy.genfromtxt." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "#Так выглядит файл sample.txt\n", "12\t;\t14\n", "32\t;\t5\n", "4\t;\t64\n", "23\t;\t21\n", "-99.9\t;\t4\n", "43\t;\t85\n", "-99.9\t;\t36\n", "-99.9\t;\t94\n", "-99.9\t;\t1\n", "213\t;\t556\n", "43\t;\t-9\n", "123\t;\t5\n", "87\t;\t-9\n", "94\t;\t51\n", "34\t;\t12\n", "-99.9\t;\t87" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "('Array1 type: ', )\n", "[ 12. 32. 4. 23. -99.9 43. -99.9 -99.9 -99.9 213. 43.\n", " 123. 87. 94. 34. -99.9]\n", "('Mean d1=', 13.031249999999998)\n", "('Array2 type: ', )\n", "[12.0 32.0 4.0 23.0 -- 43.0 -- -- -- 213.0 43.0 123.0 87.0 94.0 34.0 --]\n", "('Mean d2=', 64.36363636363636)\n" ] } ], "source": [ " # считываем значения в массив (если в файле есть пропуски-пробелы, то они не будут пропущены, а заменены на filling_values)\n", "d1 = np.genfromtxt('sample.txt',usecols=[0],unpack= True,filling_values= -99.9) # текстовый файл с данными\n", "print('Array1 type: ',type(d1))\n", "print(d1)\n", "print('Mean d1=',np.mean(d1))\n", "\n", "# считываем значения в массив-маску (usemask=True,missing_values= -99.9). \n", "# В качестве маски(то есть фильтра \"лишних\" значений) задано число -99.9\n", "# N.B. Необходимо точно указывать число (то есть -99.9, а не просто -99 или 12.0, а не просто 12)\n", "d2 = np.genfromtxt('sample.txt',usecols=[0],unpack= True,usemask=True,missing_values= -99.9) \n", "print('Array2 type: ',type(d2))\n", "print(d2)\n", "print('Mean d2=',np.mean(d2))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Если в текстовом файле есть незаполненные пропуски, то есть просто пробелы в таблице данных, то они будут заполнены на величину missing_values. Если же она не определена, то будет использоваться значение по умолчанию ('nan' для float). Если missing_values указано явно, то пропуски-пробелы не будут замеяться, и следует ожидать неправильного считывания данных при их (пропусках) наличии. Рекомендую сначала считать данные и заполнить пробелы-пропуски, указав filling_values, а затем ещё раз наложить маску, если в этом есть необходимость." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### ВАЖНО! Если одновременно определить missing_values и filling_values одинаковыми значениями, чтобы заполнить \"дырки\" значениями, на которые потом должна наложиться маска, то genfromtxt выдаст следующее." ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "#Так выглядит файл sample2.txt\n", "12\t,\t14\n", "32\t,\t5\n", "\t,\t64\n", "23\t,\t21\n", "-99.9\t,\t4\n", "43\t,\t85\n", "-99.9\t,\t36\n", "\t,\t94\n", "-99.9\t,\t1\n", "213\t,\t556\n", "43\t,\t-9\n", "123\t,\t5\n", "87\t,\t-9" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "('Array2 type: ', )\n", "[12.0 32.0 -99.9 23.0 -- 43.0 -- -99.9 -- 213.0 43.0 123.0 87.0]\n" ] } ], "source": [ "d3 = np.genfromtxt('sample2.txt',usecols=[0],unpack= True,usemask=True,missing_values= -99.9, filling_values = -99.9) \n", "print('Array2 type: ',type(d3))\n", "print(d3)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### То есть маска наложилась РАНЬШЕ, чем была проведена замена значений! То есть одновременно заполнить дырки/пропуски и отфильтровать какие-то значения не получается! Будьте внимательный!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Часто нужно получить вектор аномалий относительно среднего. С массивами-масками это делается так:" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "('Anomalies array:', masked_array(data = [-52.36363636363636 -32.36363636363636 -60.36363636363636\n", " -41.36363636363636 -- -21.36363636363636 -- -- -- 148.63636363636363\n", " -21.36363636363636 58.63636363636364 22.63636363636364 29.63636363636364\n", " -30.36363636363636 --],\n", " mask = [False False False False True False True True True False False False\n", " False False False True],\n", " fill_value = 1e+20)\n", ")\n" ] } ], "source": [ "print(type(d2))\n", "d2anom = d2.anom()\n", "print('Anomalies array:', d2anom)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Также с помошью массивов-масок можно быстро заполнить False-значения маски. Например, средними значениями." ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "('Mean d2=', 64.36363636363636)\n", "('d2 array: ', masked_array(data = [12.0 32.0 4.0 23.0 -- 43.0 -- -- -- 213.0 43.0 123.0 87.0 94.0 34.0 --],\n", " mask = [False False False False True False True True True False False False\n", " False False False True],\n", " fill_value = 1e+20)\n", ")\n", "('Gaps are filled by mean values:', array([ 12. , 32. , 4. , 23. ,\n", " 64.36363636, 43. , 64.36363636, 64.36363636,\n", " 64.36363636, 213. , 43. , 123. ,\n", " 87. , 94. , 34. , 64.36363636]))\n", "(, 64.36363636363636)\n" ] } ], "source": [ "print('Mean d2=',np.mean(d2)) # среднее \n", "print('d2 array: ',d2) \n", "\n", "d3 = d2.filled(np.mean(d2))\n", "print('Gaps are filled by mean values:',d3)\n", "print(type(d3),np.mean(d3))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Как видно из типа заполненного массива - это обычный массив, а не массив-маска. Но, конечно, на него можно опять наложить маску, снова превратив его в массив-маску." ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "\n", "[12.0 32.0 4.0 23.0 -- 43.0 -- -- -- -- 43.0 -- -- -- 34.0 --]\n" ] } ], "source": [ "maska2 = np.ma.array(d2, mask = (d2 > 80.0), copy = True) \n", "print(type(maska2))\n", "print(maska2)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 2", "language": "python", "name": "python2" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 2 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython2", "version": "2.7.8" } }, "nbformat": 4, "nbformat_minor": 0 }