web-dev-qa-db-fra.com

habillage de facettes à plusieurs colonnes / lignes dans Altair

Dans ggplot2, il est facile de créer un tracé à facettes avec des facettes qui s'étendent à la fois sur les lignes et les colonnes. Existe-t-il un moyen "simple" de le faire dans altair? facet documentation

Il est possible d'avoir le tracé des facettes dans une seule colonne,

import altair as alt
from vega_datasets import data
iris = data.iris

chart = alt.Chart(iris).mark_point().encode(
    x='petalLength:Q',
    y='petalWidth:Q',
    color='species:N'
).properties(
    width=180,
    height=180
).facet(
    row='species:N'
)

et sur une seule ligne,

chart = alt.Chart(iris).mark_point().encode(
    x='petalLength:Q',
    y='petalWidth:Q',
    color='species:N'
).properties(
    width=180,
    height=180
).facet(
    column='species:N'
)

mais souvent, je veux juste les tracer dans une grille en utilisant plus d'une colonne/ligne, c'est-à-dire que ceux qui s'alignent dans une seule colonne/ligne ne signifient rien de particulier.

Par exemple, voir facet_wrap de ggplot2: http://www.cookbook-r.com/Graphs/Facets_ (ggplot2)/# facetwrap

15
saladi

Dans Altair version 3.1 ou plus récente (publiée en juin 2019), les facettes encapsulées sont prises en charge directement dans l'API Altair. En modifiant votre exemple d'iris, vous pouvez envelopper vos facettes sur deux colonnes comme ceci:

import altair as alt
from vega_datasets import data
iris = data.iris()

alt.Chart(iris).mark_point().encode(
    x='petalLength:Q',
    y='petalWidth:Q',
    color='species:N'
).properties(
    width=180,
    height=180
).facet(
    facet='species:N',
    columns=2
)

enter image description here

Alternativement, le même graphique peut être spécifié avec la facette comme encodage:

alt.Chart(iris).mark_point().encode(
    x='petalLength:Q',
    y='petalWidth:Q',
    color='species:N',
    facet='species:N'
).properties(
    width=180,
    height=180,
    columns=2
)

L'argument colonnes peut être spécifié de la même manière pour les graphiques concaténés dans alt.concat() et les graphiques répétés alt.Chart.repeat().

4
jakevdp

Vous pouvez le faire en spécifiant .repeat() et la liste de variables row et column. C'est plus proche de facet_grid() de ggplot que facet_wrap() mais l'API est très élégante. (Voir discussion ici .) L'API est ici

iris = data.iris()

alt.Chart(iris).mark_circle().encode(
    alt.X(alt.repeat("column"), type='quantitative'),
    alt.Y(alt.repeat("row"), type='quantitative'),
    color='species:N'
).properties(
    width=250,
    height=250
).repeat(
    row=['petalLength', 'petalWidth'],
    column=['sepalLength', 'sepalWidth']
).interactive()

Ce qui produit:

enter image description here

Notez que l'ensemble est interactif en tandem (zoom avant, zoom arrière).

Assurez-vous de vérifier RepeatedCharts et FacetedCharts dans la documentation.

Création d'une grille de style facet_wrap()

Si vous souhaitez qu'un ruban de graphiques soit disposé les uns après les autres (sans nécessairement mapper une colonne ou une ligne aux variables de votre bloc de données), vous pouvez le faire en encapsulant une combinaison de hconcat() et vconcat() sur une liste de parcelles Altair.

Je suis sûr qu'il existe des moyens plus élégants, mais c'est ainsi que je l'ai fait.

Logique utilisée dans le code ci-dessous:

  1. Créez d'abord un base graphique Altair
  2. Utilisez transform_filter() pour filtrer vos données en plusieurs sous-tracés
  3. Décidez du nombre de parcelles sur une ligne et découpez cette liste
  4. Parcourez la liste des listes, en établissant une ligne à la fois.

-

import altair as alt
from vega_datasets import data
from altair.expr import datum

iris = data.iris()

base = alt.Chart(iris).mark_point().encode(
    x='petalLength:Q',
    y='petalWidth:Q',
    color='species:N'
).properties(
    width=60,
    height=60
)

#create a list of subplots
subplts = []
for pw in iris['petalWidth'].unique():
    subplts.append(base.transform_filter(datum.petalWidth == pw))


def facet_wrap(subplts, plots_per_row):
    rows = [subplts[i:i+plots_per_row] for i in range(0, len(subplts), plots_per_row)]
    compound_chart = alt.hconcat()
    for r in rows:
        rowplot = alt.vconcat() #start a new row
        for item in r:
            rowplot |= item #add suplot to current row as a new column
        compound_chart &= rowplot # add the entire row of plots as a new row
    return compound_chart


compound_chart = facet_wrap(subplts, plots_per_row=6)    
compound_chart

produire:

enter image description here

5
Ram Narasimhan

En partant de Ram's answer , et en utilisant une approche plus fonctionnelle, vous pouvez également essayer:

import altair as alt
from vega_datasets import data
from altair.expr import datum

iris = data.iris()

base = alt.Chart(iris).mark_point().encode(
    x='petalLength:Q',
    y='petalWidth:Q',
    color='species:N'
)

# chart factory
def make_chart(base_chart, pw, options):
    title = 'Petal Width {:.2f}'.format(pw)
    chart = base_chart\
      .transform_filter(datum.petalWidth == pw)\
      .properties(width=options['width'], height=options['height'], title=title)
    return chart

# create all charts
options = {'width': 50, 'height': 60}
charts = [make_chart(base, pw, options) for pw in sorted(iris['petalWidth'].unique())]

# make a single row
def make_hcc(row_of_charts):
    hconcat = [chart for chart in row_of_charts]
    hcc = alt.HConcatChart(hconcat=hconcat)
    return hcc

# take an array of charts and produce a facet grid
def facet_wrap(charts, charts_per_row):
    rows_of_charts = [
        charts[i:i+charts_per_row] 
        for i in range(0, len(charts), charts_per_row)]        
    vconcat = [make_hcc(r) for r in rows_of_charts]    
    vcc = alt.VConcatChart(vconcat=vconcat)\
      .configure_axisX(grid=True)\
      .configure_axisY(grid=True)
    return vcc

# assemble the facet grid
compound_chart = facet_wrap(charts, charts_per_row=6)
compound_chart.properties(title='My Facet grid')

Facet grid of 22 charts, 6 per row

De cette façon, il devrait être facile de modifier le code et de passer certaines options de configuration à tous vos tracés (par exemple, afficher/masquer les graduations, définir les mêmes limites inférieure/supérieure pour tous les tracés, etc.).

1
jackdbd

Voici une solution générale qui a un endroit pour ajouter des couches. Le DataFrame dans ce cas a trois colonnes et est sous forme longue.

numcols=3 # specify the number of columns you want 
all_categories=df['Category_Column'].unique() # array of strings to use as your filters and titles

rows=alt.vconcat(data=df)
numrows=int(np.ceil(len(all_categories) / numcols))
pointer=0
for _ in range(numrows):

  row=all_categories[pointer:pointer+numcols]
  cols=alt.hconcat()

  for a_chart in row:

     # add your layers here
     # line chart
     line=alt.Chart().mark_line(point=True).encode(
        x='variable',
        y='value'
     ).transform_filter(datum.Category_Column == a_chart).properties(
        title=a_chart, height=200, width=200)

     # text labels
     text=alt.Chart().mark_text().encode(
        x='variable', 
        y='value'
     ).transform_filter(datum.Category_Column == a_chart)

     both = line + text
     cols |= both

  rows &= cols
  pointer += numcols

rows

enter image description here

1
campo

J'ai trouvé qu'en faisant une concaténation de longueur supérieure à deux dans les deux sens, les données étaient déformées et tombaient par la fenêtre. J'ai résolu ce problème en décomposant récursivement le tableau de sous-intrigues en quadrants et en faisant des concaténations de lignes et de colonnes en alternance. Si vous n'avez pas ce problème, tant mieux pour vous: vous pouvez utiliser l'une des implémentations les plus simples déjà publiées. Mais si vous le faites, j'espère que cela vous aidera.

def facet_wrap(subplots, plots_per_row):
    # base cases
    if len(subplots) == 0 or plots_per_row == 0:
        return None
    if len(subplots) == 1:
        return subplots[0]

    # split subplots list into quadrants
    # we always fill top and left first
    quadrants = [[], [], [], []] # tl, tr, bl, br
    for subplot_index, subplot in enumerate(subplots):
        right_half = (subplot_index % plots_per_row) >= plots_per_row // 2
        lower_half = subplot_index >= len(subplots) / 2
        quadrants[2 * lower_half + right_half].append(subplot)

    # recurse on each quadrant
    # we want a single chart or None in place of each quadrant
    m = plots_per_row % 2 # if plots_per_row is odd then we need to split it unevenly
    quadplots = [
        facet_wrap(q, plots_per_row // 2 + m * (0 == (i % 2))) \
        for i, q in enumerate(quadrants)
    ]

    # join the quadrants
    rows = [quadplots[:2], quadplots[2:]]
    colplot = alt.hconcat()
    for row in rows:
        rowplot = alt.vconcat()
        for item in row:
            if item != None:
                rowplot = rowplot | item
        colplot &= rowplot
    return colplot
0
Barry McNamara