Как написать/прочитать Pandas DataFrame с MultiIndex из/в ASCII файл?

Я хочу иметь возможность создавать Pandas DataFrame с помощью MultiIndexes для строк и индекса столбцов и читать их из текстового файла ASCII. Мои данные выглядят так:

col_indx = MultiIndex.from_tuples([('A',  'B',  'C'), ('A',  'B',  'C2'), ('A',  'B',  'C3'), 
                                   ('A',  'B2', 'C'), ('A',  'B2', 'C2'), ('A',  'B2', 'C3'), 
                                   ('A',  'B3', 'C'), ('A',  'B3', 'C2'), ('A',  'B3', 'C3'), 
                                   ('A2', 'B',  'C'), ('A2', 'B',  'C2'), ('A2', 'B',  'C3'), 
                                   ('A2', 'B2', 'C'), ('A2', 'B2', 'C2'), ('A2', 'B2', 'C3'), 
                                   ('A2', 'B3', 'C'), ('A2', 'B3', 'C2'), ('A2', 'B3', 'C3')], 
                                   names=['one','two','three']) 
row_indx = MultiIndex.from_tuples([(0,  'North', 'M'), 
                                   (1,  'East',  'F'), 
                                   (2,  'West',  'M'), 
                                   (3,  'South', 'M'), 
                                   (4,  'South', 'F'), 
                                   (5,  'West',  'F'), 
                                   (6,  'North', 'M'), 
                                   (7,  'North', 'M'), 
                                   (8,  'East',  'F'), 
                                   (9,  'South', 'M')], 
                                   names=['n', 'location', 'sex'])
size=len(row_indx), len(col_indx)
data = np.random.randint(0,10, size)
df = DataFrame(data, index=row_indx, columns=col_indx)
print df

Я пробовал df.to_csv() и read_csv(), но они не сохраняют индекс.

Я думал о создании нового формата с использованием дополнительных разделителей. Например, используя строку ----------------, чтобы пометить конец индексов столбцов и |, чтобы отметить конец индекса строки. Таким образом, это будет выглядеть так:

one            | A   A   A   A   A   A   A   A   A  A2  A2  A2  A2  A2  A2  A2  A2  A2
two            | B   B   B  B2  B2  B2  B3  B3  B3   B   B   B  B2  B2  B2  B3  B3  B3
three          | C  C2  C3   C  C2  C3   C  C2  C3   C  C2  C3   C  C2  C3   C  C2  C3
--------------------------------------------------------------------------------------
n location sex :                                                                      
0 North    M   | 2   3   9   1   0   6   5   9   5   9   4   4   0   9   6   2   6   1
1 East     F   | 6   2   9   2   7   0   0   3   7   4   8   1   3   2   1   7   7   5
2 West     M   | 5   8   9   7   6   0   3   0   2   5   0   3   9   6   7   3   4   9
3 South    M   | 6   2   3   6   4   0   4   0   1   9   3   6   2   1   0   6   9   3
4 South    F   | 9   6   0   0   6   1   7   0   8   1   7   6   2   0   8   1   5   3
5 West     F   | 7   9   7   8   2   0   4   3   8   9   0   3   4   9   2   5   1   7
6 North    M   | 3   3   5   7   9   4   2   6   3   2   7   5   5   5   6   4   2   9
7 North    M   | 7   4   8   6   8   4   5   7   9   0   2   9   1   9   7   9   5   6
8 East     F   | 1   6   5   3   6   4   6   9   6   9   2   4   2   9   8   4   2   4
9 South    M   | 9   6   6   1   3   1   3   5   7   4   8   6   7   7   8   9   2   3

Есть ли у Pandas способ записи/чтения DataFrames в/из ASCII файлов с помощью MultiIndexes?

Ответ 1

Не знаете, какую версию pandas вы используете, но с 0.7.3 вы можете экспортировать DataFrame в TSV файл и сохранить индексы, выполнив следующие действия:

df.to_csv('mydf.tsv', sep='\t')

Причина, по которой вам нужно экспортировать в TSV по сравнению с CSV, так как заголовки столбцов имеют в них символы ,. Это должно решить первую часть вашего вопроса.

Вторая часть становится немного сложнее, поскольку, насколько я могу судить, вам нужно заранее иметь представление о том, что вы хотите, чтобы ваш DataFrame содержал. В частности, вам нужно знать:

  • Какие столбцы вашего TSV представляют строку MultiIndex
  • и что остальные столбцы также должны быть преобразованы в MultiIndex

Чтобы проиллюстрировать это, давайте вернем сохраненный выше TSV файл в новый DataFrame:

In [1]: t_df = read_table('mydf.tsv', index_col=[0,1,2])
In [2]: all(t_df.index == df.index)
Out[2]: True

Таким образом, нам удалось прочитать mydf.tsv в DataFrame, который имеет тот же индекс строки, что и исходный df. Но:

In [3]: all(t_df.columns == df.columns)
Out[3]: False

И причина здесь в том, что pandas (насколько я могу судить) не имеет возможности правильно разбора строки заголовка в MultiIndex. Как я уже упоминал выше, если вы знаете, что ваш заголовок файла TSV представляет MultiIndex, вы можете сделать следующее, чтобы исправить это:

In [4]: from ast import literal_eval
In [5]: t_df.columns = MultiIndex.from_tuples(t_df.columns.map(literal_eval).tolist(), 
                                              names=['one','two','three'])
In [6]: all(t_df.columns == df.columns)
Out[6]: True

Ответ 2

Вы можете изменить параметры печати, используя set_option:

display.multi_sparse:
: boolean
 & ЕПРС; & ЕПРС; По умолчанию True, "спарсинг" MultiIndex дисплей
 & ЕПРС; & emsp; (не отображать повторяющиеся         элементы на внешних уровнях внутри групп)

Теперь DataFrame будет напечатан по желанию:

In [11]: pd.set_option('multi_sparse', False)

In [12]: df
Out[12]: 
one             A   A   A   A   A   A   A   A   A  A2  A2  A2  A2  A2  A2  A2  A2  A2
two             B   B   B  B2  B2  B2  B3  B3  B3   B   B   B  B2  B2  B2  B3  B3  B3
three           C  C2  C3   C  C2  C3   C  C2  C3   C  C2  C3   C  C2  C3   C  C2  C3
n location sex                                                                       
0 North    M    2   1   6   4   6   4   7   1   1   0   4   3   9   2   0   0   6   4
1 East     F    3   5   5   6   4   8   0   3   2   3   9   8   1   6   7   4   7   2
2 West     M    7   9   3   5   0   1   2   8   1   6   0   7   9   9   3   2   2   4
3 South    M    1   0   0   3   5   7   7   0   9   3   0   3   3   6   8   3   6   1
4 South    F    8   0   0   7   3   8   0   8   0   5   5   6   0   0   0   1   8   7
5 West     F    6   5   9   4   7   2   5   6   1   2   9   4   7   5   5   4   3   6
6 North    M    3   3   0   1   1   3   6   3   8   6   4   1   0   5   5   5   4   9
7 North    M    0   4   9   8   5   7   7   0   5   8   4   1   5   7   6   3   6   8
8 East     F    5   6   2   7   0   6   2   7   1   2   0   5   6   1   4   8   0   3
9 South    M    1   2   0   6   9   7   5   3   3   8   7   6   0   5   4   3   5   9

Примечание: в старых версиях pandas это было pd.set_printoptions(multi_sparse=False).