значит от панд и нумпи отличаются - PullRequest
0 голосов
/ 29 октября 2018

У меня есть MEMS IMU, на котором я собираю данные, и я использую панды, чтобы получить некоторые статистические данные из него. Каждый цикл содержит 6 32-битных операций с плавающей запятой. Скорости передачи данных фиксированы для данного прогона сбора. Скорость передачи данных варьируется от 100 Гц до 1000 Гц, а время сбора составляет 72 часа. Данные сохраняются в плоском двоичном файле. Я читаю данные так:

import numpy as np
import pandas as pd
dataType=np.dtype([('a','<f4'),('b','<f4'),('c','<f4'),('d','<f4'),('e','<f4'),('e','<f4')])
df=pd.DataFrame(np.fromfile('FILENAME',dataType))
df['c'].mean()
-9.880581855773926
x=df['c'].values
x.mean()
-9.8332081

-9.833 - правильный результат. Я могу создать похожий результат, который кто-то сможет повторить так:

import numpy as np
import pandas as pd
x=np.random.normal(-9.8,.05,size=900000)
df=pd.DataFrame(x,dtype='float32',columns=['x'])
df['x'].mean()
-9.859579086303711
x.mean()
-9.8000648778888628

Я повторил это на Linux и Windows, на процессорах AMD и Intel, в Python 2.7 и 3.5. Я в тупике. Что я делаю неправильно? И получите это:

x=np.random.normal(-9.,.005,size=900000)
df=pd.DataFrame(x,dtype='float32',columns=['x'])
df['x'].mean()
-8.999998092651367
x.mean()
-9.0000075889406528

Я мог бы принять эту разницу. Это предел точности 32-битных операций с плавающей запятой.

NEVERMIND. Я написал это в пятницу, и решение пришло ко мне сегодня утром. Это проблема точности с плавающей запятой, которая усугубляется большим объемом данных. Мне нужно было преобразовать данные в 64-битную с плавающей точкой при создании кадра данных следующим образом:

df=pd.DataFrame(np.fromfile('FILENAME',dataType),dtype='float64')

Я оставлю пост, если кто-нибудь еще столкнется с подобной проблемой.

Ответы [ 2 ]

0 голосов
/ 07 ноября 2018

@ Ответ Мэтта Мессерсмита - большое исследование, но я хотел бы добавить важный момент: оба результата (numpy's и pandas ') неверны. Тем не менее, numpy имеет более высокую вероятность быть менее неправильным, чем панда.

Нет принципиальной разницы между использованием float32 и float64, однако для float32 проблемы могут наблюдаться для меньших наборов данных, чем для float64.

На самом деле не определено, как следует вычислять mean - данное математическое определение однозначно только для бесконечно точных чисел, но не для операций с плавающей запятой, которые используют наши ПК.

Так что же такое "правильная" формула?

    mean = (x0+..xn)/n 
  or 
    mean = [(x0+x1)+(x2+x3)+..]/n
  or
    mean = 1.0/n*(x0+..xn)
  and so on...

Очевидно, что при расчете на современном оборудовании все они будут давать разные результаты - в идеале можно взглянуть на формулу, которая дает наименьшую ошибку по сравнению с теоретическим правильным значением (которое рассчитывается с бесконечной точностью).

Numpy использует слегка чередующееся попарное суммирование , т.е. (((x1+x2)+(x3+x4))+(...)), что, как известно, даже если и не идеально, как известно, является довольно хорошим. С другой стороны, узкое место использует наивное суммирование x1+x2+x3+...:

REDUCE_ALL(nanmean, DTYPE0)
{
    ...
    WHILE {
        FOR {
            ai = AI(DTYPE0);
            if (ai == ai) {
                asum += ai;   <---- HERE WE GO
                count += 1;
            }
        }
        NEXT
    }
    ...
}

и мы можем легко увидеть, что происходит: после некоторых шагов, bottleneck суммирует один большой (сумма всех предыдущих элементов, пропорциональный -9.8*number_of_steps) и один маленький элемент (около -9.8), что приводит к довольно ошибка округления около big_number*eps, при этом eps равен 1e-7 для float32. Это означает, что после 10 ^ 6 суммирований мы могли бы иметь относительную ошибку около 10% (eps*10^6, это верхняя граница).

Для float64 и eps, равных 1e-16, относительная ошибка будет только около 1e-10 после 10 ^ 6 суммирования. Нам это может показаться точным, но если сравнивать с возможной точностью, это все еще фиаско!

С другой стороны, Numpy (по крайней мере, для данной серии) добавит два элемента, которые почти равны. В этом случае верхняя граница полученной относительной ошибки составляет eps*log_2(n), что составляет

  • максимум 2e-6 для float32 и 10 ^ 6 элементов
  • максимум 2e-15 для float64 и 10 ^ 6 элементов.

Из вышесказанного, среди прочего, есть следующие примечательные последствия:

  • , если среднее значение распределения составляет 0, то панды и numpy почти одинаково точны - величина суммируемых чисел составляет примерно 0.0, и между слагаемыми нет большой разницы, что привело бы к большой ошибке округления для наивное суммирование.
  • , если кто-то знает хорошую оценку среднего значения, может быть более надежным вычислить сумму x'i=xi-mean_estimate, потому что x'i будет иметь среднее значение 0.0.
  • что-то вроде x=(.333*np.ones(1000000)).astype(np.float32) достаточно, чтобы вызвать странное поведение версии панд - нет необходимости в случайности, и мы знаем, каким должен быть результат, не так ли? Важно, что 0.333 нельзя представить точно с плавающей точкой.

NB. Вышесказанное справедливо для одномерных numpy-массивов. Ситуацию сложнее суммировать вдоль оси для многомерных массивов с числами, так как иногда она превращается в наивное суммирование. Более подробное исследование см. В этом SO-post , который также объясняет @Mark Dickinson наблюдение , т. Е .:

np.ones((2, 10**8), dtype=np.float32).mean(axis=1) точны, но np.ones((10**8, 2), dtype=np.float32).mean(axis=0) не

0 голосов
/ 04 ноября 2018

Короткая версия:

Причина состоит в том, что pandas использует bottleneck (если он установлен) при вызове операции mean, а не просто полагается на numpy. bottleneck предположительно используется, поскольку он, кажется, быстрее, чем numpy (по крайней мере, на моей машине), но за счет точности. Они совпадают для 64-битной версии, но отличаются по 32-битной земле (что является интересной частью).

Длинная версия:

Чрезвычайно сложно сказать, что происходит, просто изучив исходный код этих модулей (они довольно сложны, даже для простых вычислений, таких как mean, оказывается, что численные вычисления трудны). Лучше всего использовать отладчик, чтобы избежать компиляции мозгов и подобных ошибок. Отладчик не ошибется в логике, он скажет вам точно , что происходит.

Вот некоторые из моих следов стека (значения немного отличаются, так как нет начального числа для RNG):

Может воспроизводить (Windows):

>>> import numpy as np; import pandas as pd
>>> x=np.random.normal(-9.,.005,size=900000)
>>> df=pd.DataFrame(x,dtype='float32',columns=['x'])
>>> df['x'].mean()
-9.0
>>> x.mean()
-9.0000037501099754
>>> x.astype(np.float32).mean()
-9.0000029

Ничего необычного в версии numpy не происходит. Это pandas версия, которая немного дурацкая.

Давайте посмотрим внутрь df['x'].mean():

>>> def test_it_2():
...   import pdb; pdb.set_trace()
...   df['x'].mean()
>>> test_it_2()
... # Some stepping/poking around that isn't important
(Pdb) l
2307
2308            if we have an ndarray as a value, then simply perform the operation,
2309            otherwise delegate to the object
2310
2311            """
2312 ->         delegate = self._values
2313            if isinstance(delegate, np.ndarray):
2314                # Validate that 'axis' is consistent with Series's single axis.
2315                self._get_axis_number(axis)
2316                if numeric_only:
2317                    raise NotImplementedError('Series.{0} does not implement '
(Pdb) delegate.dtype
dtype('float32')
(Pdb) l
2315                self._get_axis_number(axis)
2316                if numeric_only:
2317                    raise NotImplementedError('Series.{0} does not implement '
2318                                              'numeric_only.'.format(name))
2319                with np.errstate(all='ignore'):
2320 ->                 return op(delegate, skipna=skipna, **kwds)
2321
2322            return delegate._reduce(op=op, name=name, axis=axis, skipna=skipna,
2323                                    numeric_only=numeric_only,
2324                                    filter_type=filter_type, **kwds)

Итак, мы нашли проблемное место, но теперь все становится немного странно:

(Pdb) op
<function nanmean at 0x000002CD8ACD4488>
(Pdb) op(delegate)
-9.0
(Pdb) delegate_64 = delegate.astype(np.float64)
(Pdb) op(delegate_64)
-9.000003749978807
(Pdb) delegate.mean()
-9.0000029
(Pdb) delegate_64.mean()
-9.0000037499788075
(Pdb) np.nanmean(delegate, dtype=np.float64)
-9.0000037499788075
(Pdb) np.nanmean(delegate, dtype=np.float32)
-9.0000029

Обратите внимание, что delegate.mean() и np.nanmean вывод -9.0000029 с типом float32, , а не -9.0, как pandas nanmean. Немного возни, вы можете найти источник для pandas nanmean в pandas.core.nanops. Интересно, что на самом деле кажется, что должно сначала совпадать с numpy. Давайте посмотрим на pandas nanmean:

(Pdb) import inspect
(Pdb) src = inspect.getsource(op).split("\n")
(Pdb) for line in src: print(line)
@disallow('M8')
@bottleneck_switch()
def nanmean(values, axis=None, skipna=True):
    values, mask, dtype, dtype_max = _get_values(values, skipna, 0)

    dtype_sum = dtype_max
    dtype_count = np.float64
    if is_integer_dtype(dtype) or is_timedelta64_dtype(dtype):
        dtype_sum = np.float64
    elif is_float_dtype(dtype):
        dtype_sum = dtype
        dtype_count = dtype
    count = _get_counts(mask, axis, dtype=dtype_count)
    the_sum = _ensure_numeric(values.sum(axis, dtype=dtype_sum))

    if axis is not None and getattr(the_sum, 'ndim', False):
        the_mean = the_sum / count
        ct_mask = count == 0
        if ct_mask.any():
            the_mean[ct_mask] = np.nan
    else:
        the_mean = the_sum / count if count > 0 else np.nan

    return _wrap_results(the_mean, dtype)

Вот (короткая) версия bottleneck_switch декоратора:

import bottleneck as bn
...
class bottleneck_switch(object):

    def __init__(self, **kwargs):
        self.kwargs = kwargs

    def __call__(self, alt):
        bn_name = alt.__name__

        try:
            bn_func = getattr(bn, bn_name)
        except (AttributeError, NameError):  # pragma: no cover
            bn_func = None
    ...

                if (_USE_BOTTLENECK and skipna and
                        _bn_ok_dtype(values.dtype, bn_name)):
                    result = bn_func(values, axis=axis, **kwds)

Вызывается с alt как функция pandas nanmean, поэтому bn_name равно 'nanmean', и это attr, полученный из модуля bottleneck:

(Pdb) l
 93                             result = np.empty(result_shape)
 94                             result.fill(0)
 95                             return result
 96
 97                     if (_USE_BOTTLENECK and skipna and
 98  ->                         _bn_ok_dtype(values.dtype, bn_name)):
 99                         result = bn_func(values, axis=axis, **kwds)
100
101                         # prefer to treat inf/-inf as NA, but must compute the fun
102                         # twice :(
103                         if _has_infs(result):
(Pdb) n
> d:\anaconda3\lib\site-packages\pandas\core\nanops.py(99)f()
-> result = bn_func(values, axis=axis, **kwds)
(Pdb) alt
<function nanmean at 0x000001D2C8C04378>
(Pdb) alt.__name__
'nanmean'
(Pdb) bn_func
<built-in function nanmean>
(Pdb) bn_name
'nanmean'
(Pdb) bn_func(values, axis=axis, **kwds)
-9.0

Представьте, что декоратор bottleneck_switch() не существует ни секунды. На самом деле мы можем видеть, что вызов этого шага вручную (без bottleneck) даст вам тот же результат, что и numpy:

(Pdb) from pandas.core.nanops import _get_counts
(Pdb) from pandas.core.nanops import _get_values
(Pdb) from pandas.core.nanops import _ensure_numeric
(Pdb) values, mask, dtype, dtype_max = _get_values(delegate, skipna=skipna)
(Pdb) count = _get_counts(mask, axis=None, dtype=dtype)
(Pdb) count
900000.0
(Pdb) values.sum(axis=None, dtype=dtype) / count
-9.0000029

Это никогда не вызывается, если у вас установлен bottleneck. Вместо этого декоратор bottleneck_switch() вместо функции nanmean использует версию bottleneck. В этом и заключается несоответствие (интересно, что оно соответствует случаю float64):

(Pdb) import bottleneck as bn
(Pdb) bn.nanmean(delegate)
-9.0
(Pdb) bn.nanmean(delegate.astype(np.float64))
-9.000003749978807
Насколько я могу судить,

bottleneck используется исключительно для скорости. Я предполагаю, что они используют какой-то тип ярлыка с их функцией nanmean, но я не особо разбирался в этом (подробности по этой теме см. В ответе @ ead). Вы можете видеть, что, как правило, это немного быстрее, чем numpy по их тестам: https://github.com/kwgoodman/bottleneck. Очевидно, что цена за эту скорость - точность.

Действительно ли узкое место быстрее?

Конечно, выглядит так (по крайней мере, на моей машине).

In [1]: import numpy as np; import pandas as pd

In [2]: x=np.random.normal(-9.8,.05,size=900000)

In [3]: y_32 = x.astype(np.float32)

In [13]: %timeit np.nanmean(y_32)
100 loops, best of 3: 5.72 ms per loop

In [14]: %timeit bn.nanmean(y_32)
1000 loops, best of 3: 854 µs per loop

Для pandas было бы неплохо ввести здесь флаг (один для скорости, другой для большей точности, по умолчанию для скорости, так как это текущий impl). Некоторым пользователям важнее точность вычислений, чем скорость, с которой это происходит.

НТН.

...