У меня есть иерархические данные, которые я хотел бы визуализировать с помощью вложенных круговых диаграмм в 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
Если возможно, как я могу сделать наложение изображения, как показано ниже, буду благодарен за эту помощь, С уважением
Один из способов сделать это — определить функцию, которая создает вложенную круговую диаграмму:
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».
Обновил мой ответ с помощью фильтра
К сожалению, ваше «желание» на последнем изображении выходило за рамки моих знаний о matplotlib.
Спасибо большое, мне этого достаточно
Рад, что это помогло. Обязательно отметьте ответ как принятый, чтобы он исчез из списка вопросов без ответа. Приятного кодирования!
Но я хочу знать, нельзя ли рисовать так, как я дал наложение второго изображения?
Это то, что я сказал, что я действительно не знаю, как это сделать.
У меня просто возникла идея. Пусть мед проверит это
Извините, что снова вас беспокою, но не могли бы вы сделать что-то вроде: внутри всего круга с типами, такими как фирмикуты и протеобактерии, затем наложение второго внешнего слоя Lysinibacillus, а затем наложение третьего внешнего слоя боронитолеранами, и спасибо за ваши усилия, я очень ценю
Я обновил свой ответ, используя круговую диаграмму второго типа.