значит от панд и нумпи отличаются - программирование
Подтвердить что ты не робот

значит от панд и нумпи отличаются

У меня есть 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-х разрядных чисел.

НЕ БЕРИТЕ В ГОЛОВУ. Я написал это в пятницу, и решение поразило меня сегодня утром. Это проблема точности с плавающей запятой, которая усугубляется большим объемом данных. Мне нужно было преобразовать данные в 64-битные с плавающей точкой при создании кадра данных следующим образом:

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

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

4b9b3361

Ответ 1

Укороченная версия:

Причина этого в том, что 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 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

Обратите внимание, что np.nanmean delegate.mean() и np.nanmean выводят -9.0000029 с типом float32, а не -9.0 как nanmean делает 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 в качестве функции nanmean pandas, поэтому bn_name - это 'nanmean', и это '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 функцию 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 было бы неплохо ввести здесь флаг (один для скорости, другой для большей точности, по умолчанию для скорости, так как текущий подразумевается). Некоторым пользователям важнее точность вычислений, чем скорость, с которой это происходит.

НТН.

Ответ 2

Ответ @Matt Messersmith - это большое исследование, но я хотел бы добавить важный момент: оба результата (numpy и 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 float32 примерно 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-пост, который также объясняет наблюдение @Mark Dickinson, то есть:

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