Название: Как визуализировать иерархические данные с помощью вложенных круговых диаграмм в Python?

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

Я уже пытался реализовать это с помощью Matplotlib, но столкнулся с проблемами при фильтрации и отображении только определенных частей вложенных круговых диаграмм в зависимости от обилия определенных категорий. В частности, я хочу:

Изначально отобразите все типы. Фильтруйте и отображайте только роды, относящиеся к определенному типу (например, Firmicutes). Фильтруйте и отображайте только виды, относящиеся к определенному роду (например, Bacillus). Я попытался изменить код на основе предложений, которые нашел в Интернете, но не получил желаемого результата.

Может ли кто-нибудь предоставить руководство или пример кода о том, как добиться этой визуализации с помощью Python и Matplotlib?

Любая помощь будет принята с благодарностью. Спасибо!

import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.patches import Patch

# Read the Excel file
TissueS35_Analysis_Report = pd.read_excel("TissueS35_Analysis_Report.xlsx", sheet_name = "Species")

# Select only the 'Phylum', 'Genus', and 'Species' columns
selected_columns = TissueS35_Analysis_Report[['Phylum', 'Genus', 'Species', 'Absolute Count']]

# Group by Phylum, Genus, and Species and sum the counts
grouped_data = selected_columns.groupby(['Phylum', 'Genus', 'Species']).sum().reset_index()

# Function to generate nested pie chart data
def nested_pie(df):
    outd = {}
    for level in range(3):
        if level == 0:
            gb = df.groupby('Phylum', sort=False).sum()
        elif level == 1:
            gb = df.groupby(['Phylum', 'Genus'], sort=False).sum()
        else:
            gb = df.groupby(['Phylum', 'Genus', 'Species'], sort=False).sum()
        outd[level] = {'names': gb.index.get_level_values(level).tolist(), 'values': gb['Absolute Count'].values}
    return outd

# Generate nested pie chart data
outd = nested_pie(grouped_data)

# Plot nested donut pie chart
fig, ax = plt.subplots()

# Plot Species level (Outermost ring)
sizes = outd[2]['values']
species_colors = plt.cm.tab20c.colors
species_labels = outd[2]['names']
ax.pie(sizes, radius=1, colors=species_colors, labels=species_labels, wedgeprops=dict(width=0.3, edgecolor='w'))

# Plot Genus level (Middle ring)
sizes = outd[1]['values']
genus_colors = plt.cm.tab20b.colors
genus_labels = outd[1]['names']
ax.pie(sizes, radius=0.7, colors=genus_colors, wedgeprops=dict(width=0.3, edgecolor='w'))

# Plot Phylum level (Innermost ring)
sizes = outd[0]['values']
phylum_colors = plt.cm.tab20.colors
phylum_labels = outd[0]['names']
ax.pie(sizes, radius=0.4, colors=phylum_colors, wedgeprops=dict(width=0.3, edgecolor='w'))

# Create legend for Phylum level
legend_handles = [Patch(color=color, label=label) for color, label in zip(phylum_colors, phylum_labels)]
ax.legend(handles=legend_handles, loc='center left', bbox_to_anchor=(1, 0.5), title='Phylum')

ax.set(aspect = "equal")
plt.show()

small data refernce is  as follow 
Phylum             Genus         Species  Absolute Count
168  Proteobacteria       Pseudomonas    Unclassified           73745
152  Proteobacteria        Klebsiella    Unclassified           10777
190  Proteobacteria      Unclassified    Unclassified            4932
132  Proteobacteria   Chromobacterium    Unclassified            1840
84       Firmicutes    Lysinibacillus  boronitolerans            1780
104      Firmicutes         Weissella       ghanensis            1101
10   Actinobacteria   Corynebacterium    Unclassified             703
138  Proteobacteria       Cupriavidus        gilardii             586
93       Firmicutes    Staphylococcus    Unclassified             568
183  Proteobacteria  Stenotrophomonas      geniculata             542
Selection deleted

Если возможно, как я могу сделать наложение изображения, как показано ниже, буду благодарен за эту помощь, С уважением

Я обновил свой ответ, используя круговую диаграмму второго типа.

Serge de Gosson de Varennes 18.04.2024 15:46
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
1
71
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Один из способов сделать это — определить функцию, которая создает вложенную круговую диаграмму:

import pandas as pd
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import inset_axes

data = {
    'Phylum': ['Proteobacteria', 'Proteobacteria', 'Proteobacteria', 'Proteobacteria',
               'Firmicutes', 'Firmicutes', 'Actinobacteria', 'Proteobacteria',
               'Firmicutes', 'Proteobacteria'],
    'Genus': ['Pseudomonas', 'Klebsiella', 'Unclassified', 'Chromobacterium',
              'Lysinibacillus', 'Weissella', 'Corynebacterium', 'Cupriavidus',
              'Staphylococcus', 'Stenotrophomonas'],
    'Species': ['Unclassified', 'Unclassified', 'Unclassified', 'Unclassified',
                'boronitolerans', 'ghanensis', 'Unclassified', 'gilardii',
                'Unclassified', 'geniculata'],
    'Absolute Count': [73745, 10777, 4932, 1840, 1780, 1101, 703, 586, 568, 542]
}

df = pd.DataFrame(data)


def create_nested_pie(df):
    fig, ax = plt.subplots()
    size = 0.3
    phylum_counts = df.groupby('Phylum')['Absolute Count'].sum()
    phylum_labels = phylum_counts.index.tolist()
    ax.pie(phylum_counts, labels=phylum_labels, radius=1, wedgeprops=dict(width=size, edgecolor='w'))

    firmicutes_genus_counts = df[df['Phylum'] == 'Firmicutes'].groupby('Genus')['Absolute Count'].sum()
    firmicutes_genus_labels = firmicutes_genus_counts.index.tolist()
    ax.pie(firmicutes_genus_counts, labels=firmicutes_genus_labels, radius=1-size, wedgeprops=dict(width=size, edgecolor='w'),
           labeldistance=0.7)

    lysinibacillus_species_counts = df[(df['Phylum'] == 'Firmicutes') & (df['Genus'] == 'Lysinibacillus')].groupby('Species')['Absolute Count'].sum()
    lysinibacillus_species_labels = lysinibacillus_species_counts.index.tolist()
    ax.pie(lysinibacillus_species_counts, labels=lysinibacillus_species_labels, radius=1-2*size, wedgeprops=dict(width=size, edgecolor='w'),
           labeldistance=0.4)

    plt.show()

create_nested_pie(df)

Что дает вам:

Обновление: фильтрация

Если ваши данные большие, то фильтрацию для отображения определенных меток можно выполнить, слегка изменив функцию:

import pandas as pd
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import inset_axes

data = {
    'Phylum': ['Proteobacteria', 'Proteobacteria', 'Proteobacteria', 'Proteobacteria',
               'Firmicutes', 'Firmicutes', 'Actinobacteria', 'Proteobacteria',
               'Firmicutes', 'Proteobacteria'],
    'Genus': ['Pseudomonas', 'Klebsiella', 'Unclassified', 'Chromobacterium',
              'Lysinibacillus', 'Weissella', 'Corynebacterium', 'Cupriavidus',
              'Staphylococcus', 'Stenotrophomonas'],
    'Species': ['Unclassified', 'Unclassified', 'Unclassified', 'Unclassified',
                'boronitolerans', 'ghanensis', 'Unclassified', 'gilardii',
                'Unclassified', 'geniculata'],
    'Absolute Count': [73745, 10777, 4932, 1840, 1780, 1101, 703, 586, 568, 542]
}

df = pd.DataFrame(data)

def create_filtered_nested_pie(df, phylum_filter=None, genus_filter=None, species_filter=None):
    fig, ax = plt.subplots()
    size = 0.3
    
    if phylum_filter is not None:
        df = df[df['Phylum'].isin(phylum_filter)]
    phylum_counts = df.groupby('Phylum')['Absolute Count'].sum()
    ax.pie(phylum_counts, labels=phylum_counts.index.tolist(), radius=1, 
           wedgeprops=dict(width=size, edgecolor='w'))

    if genus_filter is not None:
        df_genus = df[df['Genus'].isin(genus_filter)]
    else:
        df_genus = df
    genus_counts = df_genus.groupby('Genus')['Absolute Count'].sum()
    ax.pie(genus_counts, labels=genus_counts.index.tolist(), radius=1-size, 
           wedgeprops=dict(width=size, edgecolor='w'), labeldistance=0.7)

    if species_filter is not None:
        df_species = df_genus[df_genus['Species'].isin(species_filter)]
    else:
        df_species = df_genus
    species_counts = df_species.groupby('Species')['Absolute Count'].sum()
    ax.pie(species_counts, labels=species_counts.index.tolist(), radius=1-2*size, 
           wedgeprops=dict(width=size, edgecolor='w'), labeldistance=0.4)

    plt.show()

create_filtered_nested_pie(df, 
                           phylum_filter=['Proteobacteria', 'Firmicutes'],
                           genus_filter=['Pseudomonas', 'Lysinibacillus'],
                           species_filter=['boronitolerans', 'Unclassified'])

что дает

Чтобы получить пропорциональную круговую диаграмму и исключить все, что не фильтруется (становясь затем прозрачным), я немного изменил свою функцию, чтобы вычислить занятую пропорцию и «скрыть» то, что не фильтруется:

import pandas as pd
import matplotlib.pyplot as plt

data = {
    'Phylum': ['Proteobacteria', 'Proteobacteria', 'Proteobacteria', 'Proteobacteria',
               'Firmicutes', 'Firmicutes', 'Actinobacteria', 'Proteobacteria',
               'Firmicutes', 'Proteobacteria'],
    'Genus': ['Pseudomonas', 'Klebsiella', 'Unclassified', 'Chromobacterium',
              'Lysinibacillus', 'Weissella', 'Corynebacterium', 'Cupriavidus',
              'Staphylococcus', 'Stenotrophomonas'],
    'Species': ['Unclassified', 'Unclassified', 'Unclassified', 'Unclassified',
                'boronitolerans', 'ghanensis', 'Unclassified', 'gilardii',
                'Unclassified', 'geniculata'],
    'Absolute Count': [3745, 10777, 4932, 1840, 1780, 1101, 703, 586, 568, 542]
}
df = pd.DataFrame(data)

def create_selective_label_pie(df, phylum_filter=None, genus_filter=None, species_filter=None):
    fig, ax = plt.subplots()
    size = 0.3

    total_phylum_counts = df.groupby('Phylum')['Absolute Count'].sum()
    phylum_colors = ['blue' if phylum in phylum_filter else 'none' for phylum in total_phylum_counts.index]
    phylum_labels = [phylum if phylum in phylum_filter else "" for phylum in total_phylum_counts.index]
    ax.pie(total_phylum_counts, labels=phylum_labels, colors=phylum_colors, radius=1,
           wedgeprops=dict(width=size, edgecolor='w'))

    total_genus_counts = df.groupby(['Phylum', 'Genus'])['Absolute Count'].sum()
    genus_colors = ['green' if (phylum in phylum_filter and genus in genus_filter) else 'none' 
                    for (phylum, genus) in total_genus_counts.index]
    genus_labels = [genus if (phylum in phylum_filter and genus in genus_filter) else ""
                    for (phylum, genus) in total_genus_counts.index]
    ax.pie(total_genus_counts, labels=genus_labels, colors=genus_colors, radius=1-size,
           wedgeprops=dict(width=size, edgecolor='w'), labeldistance=0.7)

    total_species_counts = df.groupby(['Phylum', 'Genus', 'Species'])['Absolute Count'].sum()
    species_colors = ['red' if (phylum in phylum_filter and genus in genus_filter and species in species_filter) else 'none'
                      for (phylum, genus, species) in total_species_counts.index]
    species_labels = [species if (phylum in phylum_filter and genus in genus_filter and species in species_filter) else ""
                      for (phylum, genus, species) in total_species_counts.index]
    ax.pie(total_species_counts, labels=species_labels, colors=species_colors, radius=1-2*size,
           wedgeprops=dict(width=size, edgecolor='w'), labeldistance=0.4)

    plt.title('Pie Chart with Selective Labels and Transparency')
    plt.show()

create_selective_label_pie(df, 
                           phylum_filter=['Firmicutes'],
                           genus_filter=['Weissella'],
                           species_filter=['boronitolerans'])

что дает (смехотворный пример, но показательный)

Этот


create_selective_label_pie(df, 
                           phylum_filter=['Proteobacteria', 'Firmicutes'],
                           genus_filter=['Lysinibacillus'],
                           species_filter=['boronitolerans'])

даст

Для цветовых вариаций вам придется что-то сделать самостоятельно.

Привет, спасибо за ваш ответ. По-другому хочу спросить, можно ли отображать только выбранные ярлыки. Если вы видите мой оригинальный сюжет, то на нем появляется множество ярлыков, что делает его беспорядочным. В частности, я хотел бы обозначить на сюжете «Firmicutes — Lysinibacillus-boronitolerans».

Umar 18.04.2024 15:03

Обновил мой ответ с помощью фильтра

Serge de Gosson de Varennes 18.04.2024 15:10

К сожалению, ваше «желание» на последнем изображении выходило за рамки моих знаний о matplotlib.

Serge de Gosson de Varennes 18.04.2024 15:11

Спасибо большое, мне этого достаточно

Umar 18.04.2024 15:15

Рад, что это помогло. Обязательно отметьте ответ как принятый, чтобы он исчез из списка вопросов без ответа. Приятного кодирования!

Serge de Gosson de Varennes 18.04.2024 15:16

Но я хочу знать, нельзя ли рисовать так, как я дал наложение второго изображения?

Umar 18.04.2024 15:18

Это то, что я сказал, что я действительно не знаю, как это сделать.

Serge de Gosson de Varennes 18.04.2024 15:19

У меня просто возникла идея. Пусть мед проверит это

Serge de Gosson de Varennes 18.04.2024 15:21

Извините, что снова вас беспокою, но не могли бы вы сделать что-то вроде: внутри всего круга с типами, такими как фирмикуты и протеобактерии, затем наложение второго внешнего слоя Lysinibacillus, а затем наложение третьего внешнего слоя боронитолеранами, и спасибо за ваши усилия, я очень ценю

Umar 18.04.2024 15:56

Другие вопросы по теме