Spec-Zone.ru › pandas 2

Редкие структуры данных

pandas предоставляет структуры данных для эффективного хранения разреженных данных. Они не обязательно разреженные в типичном смысле «в основном 0». Скорее, вы можете рассматривать эти объекты как «сжатые», где любые данные, соответствующие определённому значению (NaN / пропущенному значению, хотя может быть выбрано любое значение, включая 0), опускаются. Сжатые значения фактически не хранятся в массиве.

In [1]: arr = np.random.randn(10)

In [2]: arr[2:-2] = np.nan

In [3]: ts = pd.Series(pd.arrays.SparseArray(arr))

In [4]: ts
Out[4]: 
0    0.469112
1   -0.282863
2         NaN
3         NaN
4         NaN
5         NaN
6         NaN
7         NaN
8   -0.861849
9   -2.104569
dtype: Sparse[float64, nan]

Обратите внимание на тип данных, Sparse[float64, nan]. Значение nan означает, что элементы массива, которые являются nan , фактически не хранятся, хранятся только элементы, отличные от nan. Эти элементы, отличные от nan , имеют тип данных float64.

Редкие объекты существуют по причинам эффективности памяти. Предположим, у вас есть большая таблица DataFrame, в основном содержащая значения NA:

In [5]: df = pd.DataFrame(np.random.randn(10000, 4))

In [6]: df.iloc[:9998] = np.nan

In [7]: sdf = df.astype(pd.SparseDtype("float", np.nan))

In [8]: sdf.head()
Out[8]: 
     0    1    2    3
0  NaN  NaN  NaN  NaN
1  NaN  NaN  NaN  NaN
2  NaN  NaN  NaN  NaN
3  NaN  NaN  NaN  NaN
4  NaN  NaN  NaN  NaN

In [9]: sdf.dtypes
Out[9]: 
0    Sparse[float64, nan]
1    Sparse[float64, nan]
2    Sparse[float64, nan]
3    Sparse[float64, nan]
dtype: object

In [10]: sdf.sparse.density
Out[10]: 0.0002

Как вы можете видеть, плотность (процент значений, которые не были «сжаты») чрезвычайно низка. Этот разреженный объект занимает гораздо меньше памяти на диске (заархивированный) и в интерпретаторе Python.

In [11]: 'dense : {:0.2f} bytes'.format(df.memory_usage().sum() / 1e3)
Out[11]: 'dense : 320.13 bytes'

In [12]: 'sparse: {:0.2f} bytes'.format(sdf.memory_usage().sum() / 1e3)
Out[12]: 'sparse: 0.22 bytes'

Функционально их поведение должно быть почти идентичным поведению их плотных аналогов.

SparseArray

arrays.SparseArray это ExtensionArray для хранения массива разреженных значений (см. типы данных для получения дополнительной информации об расширенных массивах). Это одномерный объект, подобный ndarray, хранящий только значения, отличные от fill_value:

In [13]: arr = np.random.randn(10)

In [14]: arr[2:5] = np.nan

In [15]: arr[7:8] = np.nan

In [16]: sparr = pd.arrays.SparseArray(arr)

In [17]: sparr
Out[17]: 
[-1.9556635297215477, -1.6588664275960427, nan, nan, nan, 1.1589328886422277, 0.14529711373305043, nan, 0.6060271905134522, 1.3342113401317768]
Fill: nan
IntIndex
Indices: array([0, 1, 5, 6, 8, 9], dtype=int32)

Разреженный массив может быть преобразован в обычный (плотный) ndarray с помощью numpy.asarray()

In [18]: np.asarray(sparr)
Out[18]: 
array([-1.9557, -1.6589,     nan,     nan,     nan,  1.1589,  0.1453,
           nan,  0.606 ,  1.3342])

SparseDtype

Свойство SparseArray.dtype хранит две части информации

  1. Тип данных значений, не являющихся разреженными

  2. Скалярное значение заполнения

In [19]: sparr.dtype
Out[19]: Sparse[float64, nan]

Можно создать SparseDtype, передав только тип данных

In [20]: pd.SparseDtype(np.dtype('datetime64[ns]'))
Out[20]: Sparse[datetime64[ns], numpy.datetime64('NaT')]

в этом случае будет использоваться значение заполнения по умолчанию (для типов данных NumPy это часто «пропущенное» значение для данного типа данных). Для переопределения этого значения по умолчанию можно вместо этого указать явное значение заполнения

In [21]: pd.SparseDtype(np.dtype('datetime64[ns]'),
   ....:                fill_value=pd.Timestamp('2017-01-01'))
   ....: 
Out[21]: Sparse[datetime64[ns], Timestamp('2017-01-01 00:00:00')]

Наконец, строковый псевдоним 'Sparse[dtype]' можно использовать для указания разреженного типа данных во многих местах

In [22]: pd.array([1, 0, 0, 2], dtype='Sparse[int]')
Out[22]: 
[1, 0, 0, 2]
Fill: 0
IntIndex
Indices: array([0, 3], dtype=int32)

Доступ к разреженным данным

pandas предоставляет доступ к разреженным данным через .sparse аксессор, аналогичный .str для строковых данных, .cat для категориальных данных и .dt для данных типа datetime. Этот пространство имён предоставляет атрибуты и методы, специфичные для разреженных данных.

In [23]: s = pd.Series([0, 0, 1, 2], dtype="Sparse[int]")

In [24]: s.sparse.density
Out[24]: 0.5

In [25]: s.sparse.fill_value
Out[25]: 0

Этот аксессор доступен только для данных с SparseDtype, и для самого класса Series для создания Series с разреженными данными из матрицы scipy COO.

Аксессор .sparse был добавлен и для DataFrame. См. Аксессор разреженных данных для получения дополнительной информации.

Вычисления с разреженными данными

Вы можете применять NumPy ufuncs к arrays.SparseArray и получить arrays.SparseArray в качестве результата.

In [26]: arr = pd.arrays.SparseArray([1., np.nan, np.nan, -2., np.nan])

In [27]: np.abs(arr)
Out[27]: 
[1.0, nan, nan, 2.0, nan]
Fill: nan
IntIndex
Indices: array([0, 3], dtype=int32)

ufunc также применяется к fill_value. Это необходимо для получения правильного плотного результата.

In [28]: arr = pd.arrays.SparseArray([1., -1, -1, -2., -1], fill_value=-1)

In [29]: np.abs(arr)
Out[29]: 
[1, 1, 1, 2.0, 1]
Fill: 1
IntIndex
Indices: array([3], dtype=int32)

In [30]: np.abs(arr).to_dense()
Out[30]: array([1., 1., 1., 2., 1.])

Преобразование

Для преобразования данных из разреженного в плотный используйте .sparse аксессоры

In [31]: sdf.sparse.to_dense()
Out[31]: 
             0         1         2         3
0          NaN       NaN       NaN       NaN
1          NaN       NaN       NaN       NaN
2          NaN       NaN       NaN       NaN
3          NaN       NaN       NaN       NaN
4          NaN       NaN       NaN       NaN
...        ...       ...       ...       ...
9995       NaN       NaN       NaN       NaN
9996       NaN       NaN       NaN       NaN
9997       NaN       NaN       NaN       NaN
9998  0.509184 -0.774928 -1.369894 -0.382141
9999  0.280249 -1.648493  1.490865 -0.890819

[10000 rows x 4 columns]

Для преобразования из плотного в разреженный используйте DataFrame.astype() с SparseDtype.

In [32]: dense = pd.DataFrame({"A": [1, 0, 0, 1]})

In [33]: dtype = pd.SparseDtype(int, fill_value=0)

In [34]: dense.astype(dtype)
Out[34]: 
   A
0  1
1  0
2  0
3  1

Взаимодействие с scipy.sparse

Используйте DataFrame.sparse.from_spmatrix() для создания DataFrame с разреженными значениями из разреженной матрицы.

In [35]: from scipy.sparse import csr_matrix

In [36]: arr = np.random.random(size=(1000, 5))

In [37]: arr[arr < .9] = 0

In [38]: sp_arr = csr_matrix(arr)

In [39]: sp_arr
Out[39]: 
<1000x5 sparse matrix of type '<class 'numpy.float64'>'
	with 517 stored elements in Compressed Sparse Row format>

In [40]: sdf = pd.DataFrame.sparse.from_spmatrix(sp_arr)

In [41]: sdf.head()
Out[41]: 
          0  1  2         3  4
0   0.95638  0  0         0  0
1         0  0  0         0  0
2         0  0  0         0  0
3         0  0  0         0  0
4  0.999552  0  0  0.956153  0

In [42]: sdf.dtypes
Out[42]: 
0    Sparse[float64, 0]
1    Sparse[float64, 0]
2    Sparse[float64, 0]
3    Sparse[float64, 0]
4    Sparse[float64, 0]
dtype: object

Поддерживаются все разреженные форматы, но матрицы, которые не находятся в формате COOrdinate, будут преобразованы, копируя данные при необходимости. Для преобразования обратно в разреженную матрицу SciPy в формате COO можно использовать метод DataFrame.sparse.to_coo():

In [43]: sdf.sparse.to_coo()
Out[43]: 
<1000x5 sparse matrix of type '<class 'numpy.float64'>'
	with 517 stored elements in COOrdinate format>

Series.sparse.to_coo() реализован для преобразования Series с разреженными значениями, индексированными MultiIndex, в scipy.sparse.coo_matrix.

Метод требует MultiIndex с двумя или более уровнями.

In [44]: s = pd.Series([3.0, np.nan, 1.0, 3.0, np.nan, np.nan])

In [45]: s.index = pd.MultiIndex.from_tuples(
   ....:     [
   ....:         (1, 2, "a", 0),
   ....:         (1, 2, "a", 1),
   ....:         (1, 1, "b", 0),
   ....:         (1, 1, "b", 1),
   ....:         (2, 1, "b", 0),
   ....:         (2, 1, "b", 1),
   ....:     ],
   ....:     names=["A", "B", "C", "D"],
   ....: )
   ....: 

In [46]: ss = s.astype('Sparse')

In [47]: ss
Out[47]: 
A  B  C  D
1  2  a  0    3.0
         1    NaN
   1  b  0    1.0
         1    3.0
2  1  b  0    NaN
         1    NaN
dtype: Sparse[float64, nan]

В примере ниже мы преобразуем Series в разреженное представление двумерного массива, указав, что первый и второй MultiIndex уровни определяют метки для строк, а третий и четвёртый уровни определяют метки для столбцов. Мы также указали, что метки столбцов и строк должны быть отсортированы в окончательном разреженном представлении.

In [48]: A, rows, columns = ss.sparse.to_coo(
   ....:     row_levels=["A", "B"], column_levels=["C", "D"], sort_labels=True
   ....: )
   ....: 

In [49]: A
Out[49]: 
<3x4 sparse matrix of type '<class 'numpy.float64'>'
	with 3 stored elements in COOrdinate format>

In [50]: A.todense()
Out[50]: 
matrix([[0., 0., 1., 3.],
        [3., 0., 0., 0.],
        [0., 0., 0., 0.]])

In [51]: rows
Out[51]: [(1, 1), (1, 2), (2, 1)]

In [52]: columns
Out[52]: [('a', 0), ('a', 1), ('b', 0), ('b', 1)]

Указание различных меток строк и столбцов (и не сортируя их) приводит к другой разреженной матрице:

In [53]: A, rows, columns = ss.sparse.to_coo(
   ....:     row_levels=["A", "B", "C"], column_levels=["D"], sort_labels=False
   ....: )
   ....: 

In [54]: A
Out[54]: 
<3x2 sparse matrix of type '<class 'numpy.float64'>'
	with 3 stored elements in COOrdinate format>

In [55]: A.todense()
Out[55]: 
matrix([[3., 0.],
        [1., 3.],
        [0., 0.]])

In [56]: rows
Out[56]: [(1, 2, 'a'), (1, 1, 'b'), (2, 1, 'b')]

In [57]: columns
Out[57]: [(0,), (1,)]

Удобный метод Series.sparse.from_coo() реализован для создания Series с разреженными значениями из scipy.sparse.coo_matrix.

In [58]: from scipy import sparse

In [59]: A = sparse.coo_matrix(([3.0, 1.0, 2.0], ([1, 0, 0], [0, 2, 3])), shape=(3, 4))

In [60]: A
Out[60]: 
<3x4 sparse matrix of type '<class 'numpy.float64'>'
	with 3 stored elements in COOrdinate format>

In [61]: A.todense()
Out[61]: 
matrix([[0., 0., 1., 2.],
        [3., 0., 0., 0.],
        [0., 0., 0., 0.]])

По умолчанию (с dense_index=False) просто возвращается Series, содержащая только ненулевые элементы.

In [62]: ss = pd.Series.sparse.from_coo(A)

In [63]: ss
Out[63]: 
0  2    1.0
   3    2.0
1  0    3.0
dtype: Sparse[float64, nan]

Указание dense_index=True приведет к индексу, представляющему декартово произведение координат строк и столбцов матрицы. Обратите внимание, что это потребует значительного объёма памяти (по сравнению с dense_index=False) если разреженная матрица достаточно велика (и разреженна).

In [64]: ss_dense = pd.Series.sparse.from_coo(A, dense_index=True)

In [65]: ss_dense
Out[65]: 
1  0    3.0
   2    NaN
   3    NaN
0  0    NaN
   2    1.0
   3    2.0
   0    NaN
   2    1.0
   3    2.0
dtype: Sparse[float64, nan]

© 2008–2022, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team
Licensed under the 3-clause BSD License.
https://pandas.pydata.org/pandas-docs/version/2.2.2/user_guide/sparse.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API