Преобразование уникальных столбцов в заголовки SFrame с соответствующими значениями

У меня есть файл с разделителями табуляции:

$ echo -e 'abc\txyz\t0.9\nefg\txyz\t0.3\nlmn\topq\t0.23\nabc\tjkl\t0.5\n' > test.txt
$ cat test.txt
abc xyz 0.9
efg xyz 0.3
lmn opq 0.23
abc jkl 0.5

$ python
>>> from sframe import SFrame
>>> sf = SFrame.read_csv('test.txt', header=False, delimiter='\t', column_type_hints=[unicode, unicode, float])
[INFO] sframe.cython.cy_server: SFrame v2.1 started. Logging /tmp/sframe_server_1479718846.log
>>> sf
Columns:
    X1  str
    X2  str
    X3  float

Rows: 4

Data:
+-----+-----+------+
|  X1 |  X2 |  X3  |
+-----+-----+------+
| abc | xyz | 0.9  |
| efg | xyz | 0.3  |
| lmn | opq | 0.23 |
| abc | jkl | 0.5  |
+-----+-----+------+
[4 rows x 3 columns]

Цель состоит в том, чтобы создать другой SFrame, в котором будет одна уникальная строка, состоящая из "X1", а столбцы - это значения из "X2", то есть:

+-----+-----+-----+------+
|  X1 | xyz | opq |  jkl |
+-----+-----+-----+------+
| abc | 0.9 | 0.0 |  0.5 |
+-----+-----+-----+------+
| efg | 0.3 | 0.0 |  0.0 |
+-----+-----+-----+------+
| lmn | 0.0 | 0.23|  0.0 |
+-----+-----+-----+------+

Я пытался сделать это без SFrame:

>>> import io
>>> with io.open('test.txt', 'r', encoding='utf8') as fin:
...     for line in fin:
...             if line.strip():
...                     s,t,p = line.strip().split('\t')
...                     matrix[(s,t)] = float(p)
... 
>>> matrix
{(u'abc', u'jkl'): 0.5, (u'abc', u'xyz'): 0.9, (u'lmn', u'opq'): 0.23, (u'efg', u'xyz'): 0.3}

>>> col1, col2 = zip(*matrix.keys())
>>> [[matrix.get((c1,c2), 0.0) for c2 in col2] for c1 in col1]
[[0.5, 0.9, 0.0, 0.9], [0.5, 0.9, 0.0, 0.9], [0.0, 0.0, 0.23, 0.0], [0.0, 0.3, 0.0, 0.3]]
>>> import numpy as np
>>> np.array([[matrix.get((c1,c2), 0.0) for c2 in col2] for c1 in col1])
array([[ 0.5 ,  0.9 ,  0.  ,  0.9 ],
       [ 0.5 ,  0.9 ,  0.  ,  0.9 ],
       [ 0.  ,  0.  ,  0.23,  0.  ],
       [ 0.  ,  0.3 ,  0.  ,  0.3 ]])
>>> SFrame(np.array([[matrix.get((c1,c2), 0.0) for c2 in col2] for c1 in col1]))
Columns:
    X1  array

Rows: 4

Data:
+-----------------------+
|           X1          |
+-----------------------+
|  [0.5, 0.9, 0.0, 0.9] |
|  [0.5, 0.9, 0.0, 0.9] |
| [0.0, 0.0, 0.23, 0.0] |
|  [0.0, 0.3, 0.0, 0.3] |
+-----------------------+
[4 rows x 1 columns]

Но это все еще не дает мне желаемый SFrame. Как мне преобразовать уникальные столбцы в заголовки SFrame с соответствующими значениями? Т.е. добиться:

+-----+-----+-----+------+
|  X1 | xyz | opq |  jkl |
+-----+-----+-----+------+
| abc | 0.9 | 0.0 |  0.5 |
+-----+-----+-----+------+
| efg | 0.3 | 0.0 |  0.0 |
+-----+-----+-----+------+
| lmn | 0.0 | 0.23|  0.0 |
+-----+-----+-----+------+

Должен быть более простой способ сделать это. Представьте, что уникального нет. элементов столбцов может доходить до 1 000 000, и результирующий SFrame может иметь размер 1 000 000 X 1 000 000, таким образом, требуется структура данных, подобная SFrame или HDF, а не пустой массив или списки собственных списков Python.

1 ответ

Решение

То, что вы хотите сделать, - это действительно тривиально в пандах, используя либо df.pivot(index='X1', columns='X2', values='X3') или делая df.set_index(['X1','X2']).unstack('X2') (см. в конце этого поста).

Кажется, что ни один не существует в SFrame. (Я могу ошибаться, никогда не использовал SFrame до сих пор, но я не смог найти никаких доказательств в документации).

Вам нужно использовать SFrame.unstack() и SFrame.unpack() для достижения желаемого результата.

from sframe import SFrame
sf = SFrame.read_csv('test.txt', header=False, delimiter='\t', column_type_hints=[unicode, unicode, float])

Кулак, unstack:

sf2 = sf.unstack(['X2', 'X3'], new_column_name='dict_counts')
sf2

X1      dict_counts
efg     {'xyz': 0.3}
lmn     {'opq': 0.23}
abc     {'jkl': 0.5, 'xyz': 0.9}

Затем распакуйте:

out = sf2.unpack('dict_counts', column_name_prefix='')
out

X1      jkl     opq     xyz
efg     None    None    0.3
lmn     None    0.23    None
abc     0.5     None    0.9

Наконец, вы можете заполнить, чтобы заменить None с 0 если вы хотите:

for c in out.column_names():
    out = out.fillna(c, 0)
out


X1      jkl     opq     xyz
efg     0.0     0.0     0.3
lmn     0.0     0.23    0.0
abc     0.5     0.0     0.9

Еще один грубый способ сделать это - преобразовать в него pandas DataFrame, чтобы развернуть его, но это может не сработать, если ваш набор данных слишком большой:

import pandas as pd
from sframe import SFrame
sf = SFrame.read_csv('test.txt', header=False, delimiter='\t', column_type_hints=[unicode, unicode, float])
sf = SFrame(data=sf.to_dataframe().pivot(index='X1', columns='X2', values='X3').fillna(0).reset_index())
Другие вопросы по тегам