Как добавить месторасположение в python seaborn по оси X? - PullRequest
1 голос
/ 23 апреля 2020

В основном, я хочу воспроизвести следующий график в python (Это отсканированное изображение):

enter image description here

До сих пор у меня есть гистограмма, но я не нашел, как воспроизвести нижнюю часть с этими хорошими показателями.

Мои данные выглядят так:

id,date,Q38933,Q35805,Q767485,Q344873,Q188008,Q86,Q9690,Q40878,Q114085,Q474959,Q647099,Q485831,Q5445,Q1076369,Q3508755
1587150299663,22/04/2020,True,False,True,False,True,False,False,False,False,False,False,False,False,True,False
1587150082180,22/04/2020,False,False,True,False,True,False,False,False,False,False,False,False,False,False,False
1587150149101,22/04/2020,True,False,True,False,True,False,False,False,False,False,False,False,False,False,False
1587150390220,22/04/2020,False,False,False,False,True,True,False,False,False,False,False,False,False,False,False
1587150481217,22/04/2020,False,False,True,False,True,False,False,False,False,False,False,False,False,False,False
1587150599146,22/04/2020,False,False,True,False,True,False,False,False,False,False,False,False,False,False,False
1587150729319,22/04/2020,True,True,True,False,True,True,False,False,False,False,False,False,False,False,False

1 Ответ

2 голосов
/ 23 апреля 2020

Вот способ создать похожий сюжет, используя стандартный matplotlib. И симптомы, и происшествия сортируются, чтобы получить макет. 3 вспомогательных участка с общими осями x и y объединяются для создания полного графика.

Код предполагает, что входные данные приведены в виде списка симптомов S для N индивидуумов. 1 представляет присутствующий симптом, в противном случае 0.

Затем для каждого индивидуума симптомы S объединяются в двоичное число (через matmul со степенью двойки). Эти числа подсчитываются с помощью np.histogram и сортируются от высокого к низкому.

Некоторые детали не до конца проработаны:

  • xlabels и ylabels могут быть добавлены в соответствующих местах
  • вероятно, отступы, размеры шрифта, относительная ширина и т.д. c. нужно настроиться на конкретную ситуацию; это сильно зависит от количества симптомов и количества совпадений, чтобы показать, как вещи остаются наиболее читабельными
  • изображение в исходном посте, кажется, имеет как серые, так и черные вертикальные линии, но неясно, по какому критерию

Следующий код начинается с фрейма данных, аналогичного приведенному в обновленном вопросе. Затем он преобразуется в массив 2D numpy, пригодный для последующих вычислений и графиков.

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator

np.random.seed(13579)


def combined_number_to_list(cooc):
    ''' convert a binary number to a list of its powers of two
        e.g. 5 is converted to [0, 2] because 5 == 2**0 + 2**2
    '''
    return [i for i in range(20) if cooc & (1 << i)]

def cooccurrences_plot(symptoms, occurrences, num_cooc=30, min_cooc_count=1, color='C1'):
    ''' create a plot of cooccurrences
    :param symptoms: list of S symptoms
    :param occurrences: NxS array of occurrences of each symptom for a list of N individuals
    :param num_cooc: number of cooccurrences to show, maximum would be 2**S - 1
    :param min_cooc_count: minimum count of cooccurrences needed to be shown in the plot
    '''
    num_symp = len(symptoms)
    symp_sums = occurrences.sum(axis=0)
    symp_order = symp_sums.argsort()
    inv_symp_order = symp_order.argsort()
    combinations = np.matmul(occurrences, (2 ** np.arange(num_symp))[inv_symp_order])
    bins = np.arange(1, 2 ** num_symp + 1)
    values, _ = np.histogram(combinations, bins=bins)
    cooc_order = (-values).argsort()
    num_cooc = np.minimum(num_cooc, len(np.where(values >= min_cooc_count)[0]))

    fig, axs = plt.subplots(2, 2, sharex='col', sharey='row', figsize=(10, 5),
                            gridspec_kw={'width_ratios': [1, 4], 'height_ratios': [2, 3],
                                         'wspace': 0.25, 'hspace': 0.15, 'left': 0.04, 'right': 0.96})
    for ax in axs.ravel():
        for dir in ['left', 'right', 'top', 'bottom']:
            ax.spines[dir].set_visible(False)
    axs[0, 0].axis('off')

    axs[0, 1].bar(range(num_cooc), values[cooc_order][:num_cooc], ec='white', color=color)
    axs[0, 1].tick_params(labelbottom=True, labelleft=True, length=0)
    axs[0, 1].tick_params(axis='x', rotation=90)
    axs[0, 1].grid(True, axis='y', ls='--')
    axs[0, 1].yaxis.set_major_locator(MaxNLocator(6))
    axs[0, 1].axhline(0, color=color)

    axs[1, 0].barh(np.array(symptoms)[symp_order], symp_sums[symp_order], ec='white', color=color)
    axs[1, 0].tick_params(labelbottom=True, labelleft=False, left=False, length=0)
    axs[1, 0].invert_xaxis()
    axs[1, 0].grid(True, axis='x', ls='--')
    axs[1, 0].xaxis.set_major_locator(MaxNLocator(4))

    ax = axs[1, 1]
    ax.tick_params(labelbottom=False, labelleft=True, length=0)
    ax.set_xticks(range(num_cooc))
    ax.set_xticklabels(values[cooc_order][:num_cooc])
    ax.set_xlim(-1, num_cooc - 0.4)
    for i, cooc in enumerate(bins[cooc_order][:num_cooc]):
        ax.plot(np.full(num_symp, i), np.arange(num_symp), 'ob-', alpha=0.15, color=color)
        occ = combined_number_to_list(cooc)
        ax.plot(np.full_like(occ, i), occ, 'ob-', color=color)

N = 8000
symp_probability = np.random.uniform(0.05, 0.80, 15)
data = [[i + 1587150299663, '22/04/2020'] + list(np.random.binomial(1, symp_probability).astype(bool))
        for i in range(N)]
df = pd.DataFrame(columns=['id', 'date', 'Q38933', 'Q35805', 'Q767485', 'Q344873', 'Q188008', 'Q86', 'Q9690',
                           'Q40878', 'Q114085', 'Q474959', 'Q647099', 'Q485831', 'Q5445', 'Q1076369', 'Q3508755'],
                  data=data)
symptoms = df.columns[2:]
occurrences = df[symptoms].to_numpy()
cooccurrences_plot(symptoms, occurrences, num_cooc=50)

plt.show()

example plot

PS: более простой пример ввода:

N = 500
symptoms = ['symptom '+l for l in list('ABCDEF')]
symp_probability = np.random.uniform(0.05, 0.80, len(symptoms))
occurrences = np.random.binomial(1, symp_probability, size=(N, len(symptoms)))
cooccurrences_plot(symptoms, occurrences)

РЕДАКТИРОВАТЬ (алемол): я адаптировал решение с помощью моей таблицы CSV:

canonical_symptoms_name = {
    'Q38933': 'fiebre',
    'Q35805': 'tos',
    'Q767485': 'fallo_respiratorio',
    'Q344873': 'sdra',
    'Q188008': 'disnea',
    'Q86': 'cefalea',
    'Q9690': 'cansancio',
    'Q40878': 'diarrea',
    'Q114085': 'congestión_nasal',
    'Q474959': 'mialgia',
    'Q647099': 'hemoptisis',
    'Q485831': 'linfopenia',
    'Q5445': 'anemia',
    'Q1076369': 'tormenta_de_citocinas',
    'Q3508755': 'síndrome_gripal'
}

canonical_symptoms_order = ['Q38933','Q35805', 'Q767485','Q344873', 'Q188008', 'Q86','Q9690','Q40878','Q114085','Q474959','Q647099','Q485831','Q5445','Q1076369','Q3508755']


symptom_names = [canonical_symptoms_name[code] for code in symptoms_order]
data = pd.read_csv('/data/table.csv', sep=',', parse_dates=True,
    dtype={'id': np.string_, 'date':np.datetime64}.update({s: np.bool for s in symptom_names}))

df = pd.DataFrame(columns=['id', 'date']+symptom_names,
                  data=data)
df = df.loc[:, (df != False).any(axis=0)]
symptoms = df.columns[2:]
occurrences = df[symptoms].to_numpy()
cooccurrences_plot(symptoms, occurrences, num_cooc=50)
plt.show()

дает

enter image description here

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...