Элемент Dataframe отображается некорректно
После искусственного создания пропуска с типом np.nan весь Dataframe после этой строки выдает по индексу непонятные значения которых нет в Dataframe
import math
import numpy as np
from scipy.spatial import distance
#Создаю Датафрейм
a = [0,1] * 10
b = [1,0] * 10
df = pd.DataFrame({'S': np.arange(100,500,20),
'A': np.arange(20,80,3),
'V': np.arange(60,120,3),
'N': np.arange(986,45876,2300),
'B': a,
'B_1': b},
index=[np.arange(0,20)])
df.head()
S A V N B B_1
0 100 20 60 986 0 1
1 120 23 63 3286 1 0
2 140 26 66 5586 0 1
3 160 29 69 7886 1 0
4 180 32 72 10186 0 1
#Создаю пропуск в данных
df['S'].iloc[1] = np.nan
df.head()
S A V N B B_1
0 100.0 20 60 986 0 1
1 NaN 23. 63 3286 1 0
2 140.0 26. 66 5586 0 1
3 160.0 29 69 7886 1 0
4 180.0 32. 72 10186 0 1
#Пропуск создался корректно
Проблема: Теперь все данные в таблице которые от 2-ой строки и ниже выводятся следующим образом:
df.iloc[1].values выводит: array([ nan, 2.300e+01, 6.300e+01, 3.286e+03, 1.000e+00, 0.000e+00]) мне не понято откуда взялись эти значения. Причем если я обращусь по индексу , в таком случае все будет корректно. df.iloc[1].values[4] выводит 1 что соответствует действительности.
Если я сделаю вывод по срезу до предпоследнего элемента df.iloc[1].values[0:4] вывод также соответствует реальным данным array([ nan, 23., 63., 3286.]) . Но если я сделаю срез от начала до конца df.iloc[1].values[0:5] в таком случае я опять получаю несуществующие данные array([ nan, 2.300e+01, 6.300e+01, 3.286e+03, 1.000e+00]) .Данная проблема актуальна для всех строк от той в которой я создал пропущенное значение np.nan. С первой строкой всё в порядке , можете проверить. Если вас не затруднит покажите пожалуйста как мне нужно было делать чтобы получить реальные значения из таблице.
Ответы (2 шт):
Pandas использует "под капотом" для работами с числами Numpy и его типы.
Когда вы берёте values от Pandas Series, вы получаете на выход массив Numpy (кажется, кроме того случая, когда вы работаете со строками).
Так получилось, что NaN в Numpy имеет тип numpy.float64, "целочисленного" NaN пока ещё не сделали, хотя и обещали в будущих версиях Pandas.
Соответственно, вы получаете на выходе массив чисел с плавающей точкой, которые на печати обычно представляются в том виде, который вы и наблюдаете. Это те же самые целые числа, которые вы поместили в DataFrame, просто они преобразовались в числа с плавающей точкой функцией values.
Массивы Numpy в отличие от списков Python не могут иметь разные типы для разных элементов массива, тип массива "повышается" до такого, в который "влезут" все элементы, которые мы пытаемся в него поместить, в данном случае это numpy.float64.
Причина в том что в Numpy матрицах для всех элементов матрицы используется один единственный тип данных. Кроме того целочисленные типы данных в Numpy не умеют хранить значение NaN и как только хотя бы одному элементу массива присваивается значение NaN, то тип данных автоматически повышается до float64. Pandas DataFrame - это коллекция независимых друг от друга Series, которые по сути являются именоваными и индексированными одномерными Numpy матрицами - векторами. Поэтому когда вы присваиваете одной ячейке DataFrame значение NaN - тип данных меняется только в одном столбце, содержащем ячейку.
Пример для наглядности:
создадим целочисленный DF:
In [19]: d = pd.DataFrame({'a': [1,2], 'b':[3,4]})
In [20]: d
Out[20]:
a b
0 1 3
1 2 4
In [21]: d.dtypes
Out[21]:
a int64
b int64 # <--- NOTE !
dtype: object
In [22]: d.values
Out[22]:
array([[1, 3],
[2, 4]], dtype=int64)
# NOTE ------------> ^^^^^
теперь присвоим одной ячейке значение NaN и посмотрим что получится:
In [23]: d.loc[1, 'b'] = np.nan
In [24]: d
Out[24]:
a b
0 1 3.0
1 2 NaN
In [25]: d.dtypes
Out[25]:
a int64
b float64 # <--- NOTE !
dtype: object
In [26]: d.values
Out[26]:
array([[ 1., 3.],
[ 2., nan]])
In [27]: d.to_numpy()
Out[27]:
array([[ 1., 3.],
[ 2., nan]])
In [28]: d.values.dtype
Out[28]: dtype('float64')
# NOTE ------> ^^^^^^^
UPDATE: по поводу различного представления одного и того же числа в Python:
практически любое число можно выразить или приблизить с точностью до маленького эпсилон при помощи экспоненциальной записи:
In [35]: 3286 - 3.286e3
Out[35]: 0.0
In [36]: 0.0123 - 1.23e-2
Out[36]: 0.0
In [37]: 0.1 + 0.2
Out[37]: 0.30000000000000004
In [38]: 0.1 + 0.2 - 0.3
Out[38]: 5.551115123125783e-17 # <--- это значение стремящееся к нулю
5.55 в степени (-17), или 1 / 5.55^17 равно нулю с точностью до 17-го знака после десятичной точки