Как создать вложенную сгруппированную гистограмму с помощью Altair? - Добавлены образцы данных

<table class="table table-bordered table-hover table-condensed">
<thead><tr><th title="Field #1">year</th>
<th title="Field #2">primary_type</th>
<th title="Field #3">Number_of_Incidents</th>
<th title="Field #4">number_of_arrests</th>
<th title="Field #5">percent_arrest</th>
<th title="Field #6">rank</th>
</tr></thead>
<tbody><tr>
<td align="right">2018</td>
<td>THEFT</td>
<td align="right">57330</td>
<td align="right">5503</td>
<td align="right">9.6</td>
<td align="right">1</td>
</tr>
<tr>
<td align="right">2018</td>
<td>BATTERY</td>
<td align="right">44667</td>
<td align="right">8886</td>
<td align="right">19.89</td>
<td align="right">2</td>
</tr>
<tr>
<td align="right">2018</td>
<td>CRIMINAL DAMAGE</td>
<td align="right">24889</td>
<td align="right">1498</td>
<td align="right">6.02</td>
<td align="right">3</td>
</tr>
<tr>
<td align="right">2018</td>
<td>ASSAULT</td>
<td align="right">18229</td>
<td align="right">2931</td>
<td align="right">16.08</td>
<td align="right">4</td>
</tr>
<tr>
<td align="right">2018</td>
<td>DECEPTIVE PRACTICE</td>
<td align="right">15879</td>
<td align="right">713</td>
<td align="right">4.49</td>
<td align="right">5</td>
</tr>
<tr>
<td align="right">2017</td>
<td>THEFT</td>
<td align="right">64334</td>
<td align="right">6459</td>
<td align="right">10.04</td>
<td align="right">1</td>
</tr>
<tr>
<td align="right">2017</td>
<td>BATTERY</td>
<td align="right">49213</td>
<td align="right">10060</td>
<td align="right">20.44</td>
<td align="right">2</td>
</tr>
<tr>
<td align="right">2017</td>
<td>CRIMINAL DAMAGE</td>
<td align="right">29040</td>
<td align="right">1747</td>
<td align="right">6.02</td>
<td align="right">3</td>
</tr>
<tr>
<td align="right">2017</td>
<td>ASSAULT</td>
<td align="right">19298</td>
<td align="right">3455</td>
<td align="right">17.9</td>
<td align="right">4</td>
</tr>
<tr>
<td align="right">2017</td>
<td>DECEPTIVE PRACTICE</td>
<td align="right">18816</td>
<td align="right">805</td>
<td align="right">4.28</td>
<td align="right">5</td>
</tr>
<tr>
<td align="right">2016</td>
<td>THEFT</td>
<td align="right">61600</td>
<td align="right">6518</td>
<td align="right">10.58</td>
<td align="right">1</td>
</tr>
<tr>
<td align="right">2016</td>
<td>BATTERY</td>
<td align="right">50292</td>
<td align="right">10328</td>
<td align="right">20.54</td>
<td align="right">2</td>
</tr>
<tr>
<td align="right">2016</td>
<td>CRIMINAL DAMAGE</td>
<td align="right">31018</td>
<td align="right">1668</td>
<td align="right">5.38</td>
<td align="right">3</td>
</tr>
<tr>
<td align="right">2016</td>
<td>ASSAULT</td>
<td align="right">18738</td>
<td align="right">3490</td>
<td align="right">18.63</td>
<td align="right">4</td>
</tr>
<tr>
<td align="right">2016</td>
<td>DECEPTIVE PRACTICE</td>
<td align="right">18733</td>
<td align="right">815</td>
<td align="right">4.35</td>
<td align="right">5</td>
</tr>
<tr>
<td align="right">2015</td>
<td>THEFT</td>
<td align="right">57335</td>
<td align="right">6771</td>
<td align="right">11.81</td>
<td align="right">1</td>
</tr>
<tr>
<td align="right">2015</td>
<td>BATTERY</td>
<td align="right">48918</td>
<td align="right">11558</td>
<td align="right">23.63</td>
<td align="right">2</td>
</tr>
<tr>
<td align="right">2015</td>
<td>CRIMINAL DAMAGE</td>
<td align="right">28675</td>
<td align="right">1835</td>
<td align="right">6.4</td>
<td align="right">3</td>
</tr>
<tr>
<td align="right">2015</td>
<td>NARCOTICS</td>
<td align="right">23883</td>
<td align="right">23875</td>
<td align="right">99.97</td>
<td align="right">4</td>
</tr>
<tr>
<td align="right">2015</td>
<td>OTHER OFFENSE</td>
<td align="right">17552</td>
<td align="right">4795</td>
<td align="right">27.32</td>
<td align="right">5</td>
</tr>
<tr>
<td align="right">2014</td>
<td>THEFT</td>
<td align="right">61561</td>
<td align="right">7415</td>
<td align="right">12.04</td>
<td align="right">1</td>
</tr>
<tr>
<td align="right">2014</td>
<td>BATTERY</td>
<td align="right">49447</td>
<td align="right">12517</td>
<td align="right">25.31</td>
<td align="right">2</td>
</tr>
<tr>
<td align="right">2014</td>
<td>NARCOTICS</td>
<td align="right">29116</td>
<td align="right">29000</td>
<td align="right">99.6</td>
<td align="right">3</td>
</tr>
<tr>
<td align="right">2014</td>
<td>CRIMINAL DAMAGE</td>
<td align="right">27798</td>
<td align="right">2095</td>
<td align="right">7.54</td>
<td align="right">4</td>
</tr>
<tr>
<td align="right">2014</td>
<td>OTHER OFFENSE</td>
<td align="right">16979</td>
<td align="right">4159</td>
<td align="right">24.49</td>
<td align="right">5</td>
</tr>
<tr>
<td align="right">2013</td>
<td>THEFT</td>
<td align="right">71530</td>
<td align="right">7727</td>
<td align="right">10.8</td>
<td align="right">1</td>
</tr>
<tr>
<td align="right">2013</td>
<td>BATTERY</td>
<td align="right">54002</td>
<td align="right">12927</td>
<td align="right">23.94</td>
<td align="right">2</td>
</tr>
<tr>
<td align="right">2013</td>
<td>NARCOTICS</td>
<td align="right">34127</td>
<td align="right">33819</td>
<td align="right">99.1</td>
<td align="right">3</td>
</tr>
<tr>
<td align="right">2013</td>
<td>CRIMINAL DAMAGE</td>
<td align="right">30853</td>
<td align="right">2107</td>
<td align="right">6.83</td>
<td align="right">4</td>
</tr>
<tr>
<td align="right">2013</td>
<td>OTHER OFFENSE</td>
<td align="right">17993</td>
<td align="right">3400</td>
<td align="right">18.9</td>
<td align="right">5</td>
</tr>
</tbody></table>

У меня уникальная ситуация. В моих данных есть год, и в каждом году у меня есть 5 категорий, и это соответствует подсчетам. Мне удалось создать столбчатую диаграмму с накоплением этих 5 категорий за соответствующий год. Однако я хочу отображать 5 смежных столбцов для каждого года на оси абсцисс. По оси Y должна отображаться соответствующая сумма для каждого столбца.

Вот мой текущий вид и код:

import altair as alt


base = alt.Chart(q13a).encode(
alt.X('year:O',
    axis=alt.Axis(format='%b', title='Year'),
    scale=alt.Scale(zero=False)
),
tooltip = ['year']
)
bar = base.mark_bar().encode(
alt.Y('sum(Number_of_Incidents)',
    axis=alt.Axis(title='sum(Number_of_Incidents)'))
,color=alt.Color('primary_type')
#tooltip = ['primary_type']
)
line =  base.mark_line(color='red').encode(
#y='No_of_repos',
alt.Y('percent_arrest',
    axis=alt.Axis(title='percent_arrest')),
)
alt.layer(
bar
).resolve_scale(
y='independent'
).properties(
height=500,
width=1200
)

введите здесь описание изображения

Мой вопрос: вместо столбца с накоплением, как я могу создать 5 смежных столбцов для каждого года на моей оси x?

Заранее благодарны за Вашу помощь.


person Guddi    schedule 28.11.2018    source источник
comment
Привет, я думаю, вам нужно указать год в кодировке column и primary_type в качестве оси x, как в этом примере из документации: altair-viz.github.io/gallery/grouped_bar_chart.html Поскольку вы не предоставляете свои данные, вам трудно помочь напрямую. Не могли бы вы предоставить часть своего набора данных?   -  person FlorianGD    schedule 28.11.2018
comment
Привет Флориан! Спасибо за ответ. См. Прилагаемый образец данных. Кроме того, мне было интересно, можем ли мы отсортировать эти столбцы по соответствующему рангу из столбца рангов в данных. Спасибо за помощь.   -  person Guddi    schedule 28.11.2018


Ответы (2)


Единственный способ сделать столбцы бок о бок - использовать кодировку столбца вместе с кодировкой x. Вы можете следовать примеру сгруппированной гистограммы из галереи примеров Альтаира здесь: https://altair-viz.github.io/gallery/grouped_bar_chart.html

Результат для ваших данных будет примерно таким:

import altair as alt
import pandas as pd
import numpy as np

q13a = pd.DataFrame({
    'year': np.random.randint(2000, 2018, 100),
    'Number_of_Incidents': np.random.randint(0, 20, 100),
    'percent_arrest': np.random.rand(100),
    'primary_type': np.random.choice(['a', 'b', 'c', 'd', 'e'], 100)
})

alt.Chart(q13a).mark_bar().encode(
    x=alt.X('primary_type', scale=alt.Scale(rangeStep=8), title=None),
    y=alt.Y('sum(Number_of_Incidents)', title='sum(Number_of_Incidents)'),
    color='primary_type',
    column='year',
    tooltip=['year']
).configure_view(
    stroke='transparent'
)

введите здесь описание изображения

person jakevdp    schedule 28.11.2018
comment
Супер классно!! Большое спасибо за Вашу помощь!! - person Guddi; 28.11.2018
comment
Привет, jakevdp, но я вижу одну проблему в диаграмме. Я заметил, что "primarty_type=d" отсутствует в первой группе, а "primarty_type=d,e" отсутствует во второй группе, и я получил пустое место для этого. В идеале в моих данных есть фиксированные 5 значений primarty_type для каждого года. Однако в 2001 году может быть primarty_type d, но в 2002 году d заменяется на p. В этом сценарии я вижу пустое место для d в 2002 году. Мой вопрос заключается в том, как показать только существующие значения на сгруппированной гистограмме и пропустить пустое место. - person Guddi; 28.11.2018
comment
Если вы добавите .resolve_scale(x='independent'), категории без записей не будут отображаться ... аналогично вопросу здесь: github.com/altair-viz/altair/issues/1221 - person jakevdp; 28.11.2018
comment
Спасибо, сработало! Последний вопрос - как отсортировать эти гистограммы в течение соответствующего года на основе значения ранга столбца? Столбец rank имеет значение от 1 до 5 для каждого primary_type за этот год. - person Guddi; 28.11.2018
comment
Внутри alt.X() использования, например sort=alt.EncodingSortField(op='sum', field='Number_of_Incidents') - person jakevdp; 29.11.2018
comment
Спасибо! Я также немного поиграюсь с ним, чтобы добавить вторую ось Y, но это не работает ... вот мой код. - person Guddi; 29.11.2018
comment
Мне интересно ... что мне следует изменить в моем коде, чтобы получить строку с процентом арестов за каждое преступление за соответствующий год? возможно ли это сделать? Также в этом коде не работает сортировка. :( Заранее спасибо за помощь. - person Guddi; 29.11.2018
comment
Предлагаю задать другие вопросы в отдельном посте. Комментарии не подходят для чтения длинных блоков кода. - person jakevdp; 29.11.2018

@jakevdp уже ответил, но чтобы ответить на ваш последний вопрос (даже если вы должны задать несколько вопросов, если у вас несколько баллов), вы можете отсортировать столбцы с помощью alt.EncodingSortField.

import altair as alt

alt.Chart(q13a).mark_bar().encode(
    x=alt.X('primary_type:N', 
            scale=alt.Scale(rangeStep=8), 
            axis=None,
            sort=alt.EncodingSortField(field='Number_of_Incident', 
                                       op='sum', 
                                       order='ascending')
           ),
    y=alt.Y('sum(Number_of_Incidents):Q', title='sum(Number_of_Incidents)'),
    color='primary_type:N',
    tooltip=['primary_type:N', 
             'year:O', 
             alt.Tooltip('sum(Number_of_Incidents):Q',
                         title='Number of incidents')
            ]
).facet(
    column=alt.Column('year:O', 
                      header=alt.Header(title=None)),
).resolve_scale(
    x='independent'
).configure_view(
    stroke='transparent'
)

altair-sorted-grouped-bar-chart


Данные

import pandas as pd
from io import StringIO

q13a = pd.read_table(StringIO("""year   primary_type    Number_of_Incidents number_of_arrests   percent_arrest  rank
2018    THEFT   57330   5503    9.6     1
2018    BATTERY     44667   8886    19.89   2
2018    CRIMINAL DAMAGE     24889   1498    6.02    3
2018    ASSAULT     18229   2931    16.08   4
2018    DECEPTIVE PRACTICE  15879   713     4.49    5
2017    THEFT   64334   6459    10.04   1
2017    BATTERY     49213   10060   20.44   2
2017    CRIMINAL DAMAGE     29040   1747    6.02    3
2017    ASSAULT     19298   3455    17.9    4
2017    DECEPTIVE PRACTICE  18816   805     4.28    5
2016    THEFT   61600   6518    10.58   1
2016    BATTERY     50292   10328   20.54   2
2016    CRIMINAL DAMAGE     31018   1668    5.38    3
2016    ASSAULT     18738   3490    18.63   4
2016    DECEPTIVE PRACTICE  18733   815     4.35    5
2015    THEFT   57335   6771    11.81   1
2015    BATTERY     48918   11558   23.63   2
2015    CRIMINAL DAMAGE     28675   1835    6.4     3
2015    NARCOTICS   23883   23875   99.97   4
2015    OTHER OFFENSE   17552   4795    27.32   5
2014    THEFT   61561   7415    12.04   1
2014    BATTERY     49447   12517   25.31   2
2014    NARCOTICS   29116   29000   99.6    3
2014    CRIMINAL DAMAGE     27798   2095    7.54    4
2014    OTHER OFFENSE   16979   4159    24.49   5
2013    THEFT   71530   7727    10.8    1
2013    BATTERY     54002   12927   23.94   2
2013    NARCOTICS   34127   33819   99.1    3
2013    CRIMINAL DAMAGE     30853   2107    6.83    4
2013    OTHER OFFENSE   17993   3400    18.9    5"""))

Создано 28 ноября 2018 г. с помощью пакета REPEXPY

person FlorianGD    schedule 28.11.2018
comment
Спасибо @FlorianGD. Я немного поиграю с этим кодом, пытаясь добавить вторую ось Y. Но это не работает .. любая идея почему? Вот мой код .. - person Guddi; 29.11.2018
comment
Привет, я призываю вас задать еще один вопрос по конкретной проблеме. - person FlorianGD; 29.11.2018