2016-01-05 7 views
13

Zastanawiam się, czy możliwe jest utworzenie wykresu liczby Seaborn, ale zamiast rzeczywistych zliczeń na osi Y, pokaż względną częstotliwość (procent) w obrębie grupy (jak określono dla parametr hue).Plamka Seaborn ze znormalizowaną osią y dla grupy

I jakby to ustalone z następującym podejściem, ale nie mogę sobie wyobrazić, jest to najprostszy podejście:

# Plot percentage of occupation per income class 
grouped = df.groupby(['income'], sort=False) 
occupation_counts = grouped['occupation'].value_counts(normalize=True, sort=False) 

occupation_data = [ 
    {'occupation': occupation, 'income': income, 'percentage': percentage*100} for 
    (income, occupation), percentage in dict(occupation_counts).items() 
] 

df_occupation = pd.DataFrame(occupation_data) 

p = sns.barplot(x="occupation", y="percentage", hue="income", data=df_occupation) 
_ = plt.setp(p.get_xticklabels(), rotation=90) # Rotate labels 

Wynik:

Percentage plot with seaborn

Używam studni znany dorosły zestaw danych z UCI machine learning repository. Pandy dataframe jest tworzony tak:

# Read the adult dataset 
df = pd.read_csv(
    "data/adult.data", 
    engine='c', 
    lineterminator='\n', 

    names=['age', 'workclass', 'fnlwgt', 'education', 'education_num', 
      'marital_status', 'occupation', 'relationship', 'race', 'sex', 
      'capital_gain', 'capital_loss', 'hours_per_week', 
      'native_country', 'income'], 
    header=None, 
    skipinitialspace=True, 
    na_values="?" 
) 

This question jest rodzajem podobne, ale nie wykorzysta parametru hue. I w moim przypadku nie mogę po prostu zmienić etykiet na osi Y, ponieważ wysokość paska musi zależeć od grupy.

+1

Czy mógłbyś dzielić się danymi http://stackoverflow.com/help/mcve? – Stefan

+0

Nie ma problemu! Dodałem link i przykładowy kod. –

+0

Musiałem zrobić to samo, co ty, i skończyłem robiąc to samo, co ty, aby osiągnąć to, co chciałem (zliczaj, twórz nowe df, wypisz jako barplot). Więc nadal jestem zainteresowany, czy jest lepszy sposób na zrobienie tego. – spacetyper

Odpowiedz

3

Mogę być zdezorientowany. Różnica między wyjściem a wyjściem

occupation_counts = (df.groupby(['income'])['occupation'] 
        .value_counts(normalize=True) 
        .rename('percentage') 
        .mul(100) 
        .reset_index() 
        .sort_values('occupation')) 
p = sns.barplot(x="occupation", y="percentage", hue="income", data=occupation_counts) 
_ = plt.setp(p.get_xticklabels(), rotation=90) # Rotate labels 

jest, wydaje mi się, tylko kolejność kolumn.

enter image description here

I wydają się przejmować, że skoro przechodzą sort=False. Ale w twoim kodzie kolejność jest określana wyjątkowo przez przypadek (a kolejność, w której słownik jest iterowany, zmienia się nawet z uruchomienia na Python 3.5).

3

Zadziwiło mnie to, że Seaborn nie zapewnia czegoś takiego po wyjęciu z pudełka.

Mimo to dość łatwo było poprawić kod źródłowy, aby uzyskać to, co chciał. Poniższy kod, z funkcją "percentageplot (x, hue, data)" działa tak jak sns.countplot, ale normuje każdy słupek na grupę (tj. Dzieli każdą wartość zielonego słupka przez sumę wszystkich zielonych pasków)

w efekcie okazuje to (trudne do interpretacji z powodu różnej N Apple vs Android): sns.countplot w to (unormowanych tak że pręty odzwierciedlać proporcji sumie dla Apple vs Android): Percentageplot

nadzieję, że to pomaga !!

from seaborn.categorical import _CategoricalPlotter, remove_na 
import matplotlib as mpl 

class _CategoricalStatPlotter(_CategoricalPlotter): 

    @property 
    def nested_width(self): 
     """A float with the width of plot elements when hue nesting is used.""" 
     return self.width/len(self.hue_names) 

    def estimate_statistic(self, estimator, ci, n_boot): 

     if self.hue_names is None: 
      statistic = [] 
      confint = [] 
     else: 
      statistic = [[] for _ in self.plot_data] 
      confint = [[] for _ in self.plot_data] 

     for i, group_data in enumerate(self.plot_data): 
      # Option 1: we have a single layer of grouping 
      # -------------------------------------------- 

      if self.plot_hues is None: 

       if self.plot_units is None: 
        stat_data = remove_na(group_data) 
        unit_data = None 
       else: 
        unit_data = self.plot_units[i] 
        have = pd.notnull(np.c_[group_data, unit_data]).all(axis=1) 
        stat_data = group_data[have] 
        unit_data = unit_data[have] 

       # Estimate a statistic from the vector of data 
       if not stat_data.size: 
        statistic.append(np.nan) 
       else: 
        statistic.append(estimator(stat_data, len(np.concatenate(self.plot_data)))) 

       # Get a confidence interval for this estimate 
       if ci is not None: 

        if stat_data.size < 2: 
         confint.append([np.nan, np.nan]) 
         continue 

        boots = bootstrap(stat_data, func=estimator, 
             n_boot=n_boot, 
             units=unit_data) 
        confint.append(utils.ci(boots, ci)) 

      # Option 2: we are grouping by a hue layer 
      # ---------------------------------------- 

      else: 
       for j, hue_level in enumerate(self.hue_names): 
        if not self.plot_hues[i].size: 
         statistic[i].append(np.nan) 
         if ci is not None: 
          confint[i].append((np.nan, np.nan)) 
         continue 

        hue_mask = self.plot_hues[i] == hue_level 
        group_total_n = (np.concatenate(self.plot_hues) == hue_level).sum() 
        if self.plot_units is None: 
         stat_data = remove_na(group_data[hue_mask]) 
         unit_data = None 
        else: 
         group_units = self.plot_units[i] 
         have = pd.notnull(
          np.c_[group_data, group_units] 
          ).all(axis=1) 
         stat_data = group_data[hue_mask & have] 
         unit_data = group_units[hue_mask & have] 

        # Estimate a statistic from the vector of data 
        if not stat_data.size: 
         statistic[i].append(np.nan) 
        else: 
         statistic[i].append(estimator(stat_data, group_total_n)) 

        # Get a confidence interval for this estimate 
        if ci is not None: 

         if stat_data.size < 2: 
          confint[i].append([np.nan, np.nan]) 
          continue 

         boots = bootstrap(stat_data, func=estimator, 
              n_boot=n_boot, 
              units=unit_data) 
         confint[i].append(utils.ci(boots, ci)) 

     # Save the resulting values for plotting 
     self.statistic = np.array(statistic) 
     self.confint = np.array(confint) 

     # Rename the value label to reflect the estimation 
     if self.value_label is not None: 
      self.value_label = "{}({})".format(estimator.__name__, 
               self.value_label) 

    def draw_confints(self, ax, at_group, confint, colors, 
         errwidth=None, capsize=None, **kws): 

     if errwidth is not None: 
      kws.setdefault("lw", errwidth) 
     else: 
      kws.setdefault("lw", mpl.rcParams["lines.linewidth"] * 1.8) 

     for at, (ci_low, ci_high), color in zip(at_group, 
               confint, 
               colors): 
      if self.orient == "v": 
       ax.plot([at, at], [ci_low, ci_high], color=color, **kws) 
       if capsize is not None: 
        ax.plot([at - capsize/2, at + capsize/2], 
          [ci_low, ci_low], color=color, **kws) 
        ax.plot([at - capsize/2, at + capsize/2], 
          [ci_high, ci_high], color=color, **kws) 
      else: 
       ax.plot([ci_low, ci_high], [at, at], color=color, **kws) 
       if capsize is not None: 
        ax.plot([ci_low, ci_low], 
          [at - capsize/2, at + capsize/2], 
          color=color, **kws) 
        ax.plot([ci_high, ci_high], 
          [at - capsize/2, at + capsize/2], 
          color=color, **kws) 

class _BarPlotter(_CategoricalStatPlotter): 
    """Show point estimates and confidence intervals with bars.""" 

    def __init__(self, x, y, hue, data, order, hue_order, 
       estimator, ci, n_boot, units, 
       orient, color, palette, saturation, errcolor, errwidth=None, 
       capsize=None): 
     """Initialize the plotter.""" 
     self.establish_variables(x, y, hue, data, orient, 
           order, hue_order, units) 
     self.establish_colors(color, palette, saturation) 
     self.estimate_statistic(estimator, ci, n_boot) 

     self.errcolor = errcolor 
     self.errwidth = errwidth 
     self.capsize = capsize 

    def draw_bars(self, ax, kws): 
     """Draw the bars onto `ax`.""" 
     # Get the right matplotlib function depending on the orientation 
     barfunc = ax.bar if self.orient == "v" else ax.barh 
     barpos = np.arange(len(self.statistic)) 

     if self.plot_hues is None: 

      # Draw the bars 
      barfunc(barpos, self.statistic, self.width, 
        color=self.colors, align="center", **kws) 

      # Draw the confidence intervals 
      errcolors = [self.errcolor] * len(barpos) 
      self.draw_confints(ax, 
           barpos, 
           self.confint, 
           errcolors, 
           self.errwidth, 
           self.capsize) 

     else: 

      for j, hue_level in enumerate(self.hue_names): 

       # Draw the bars 
       offpos = barpos + self.hue_offsets[j] 
       barfunc(offpos, self.statistic[:, j], self.nested_width, 
         color=self.colors[j], align="center", 
         label=hue_level, **kws) 

       # Draw the confidence intervals 
       if self.confint.size: 
        confint = self.confint[:, j] 
        errcolors = [self.errcolor] * len(offpos) 
        self.draw_confints(ax, 
             offpos, 
             confint, 
             errcolors, 
             self.errwidth, 
             self.capsize) 

    def plot(self, ax, bar_kws): 
     """Make the plot.""" 
     self.draw_bars(ax, bar_kws) 
     self.annotate_axes(ax) 
     if self.orient == "h": 
      ax.invert_yaxis() 

def percentageplot(x=None, y=None, hue=None, data=None, order=None, hue_order=None, 
       orient=None, color=None, palette=None, saturation=.75, 
       ax=None, **kwargs): 

    # Estimator calculates required statistic (proportion)   
    estimator = lambda x, y: (float(len(x))/y)*100 
    ci = None 
    n_boot = 0 
    units = None 
    errcolor = None 

    if x is None and y is not None: 
     orient = "h" 
     x = y 
    elif y is None and x is not None: 
     orient = "v" 
     y = x 
    elif x is not None and y is not None: 
     raise TypeError("Cannot pass values for both `x` and `y`") 
    else: 
     raise TypeError("Must pass values for either `x` or `y`") 

    plotter = _BarPlotter(x, y, hue, data, order, hue_order, 
          estimator, ci, n_boot, units, 
          orient, color, palette, saturation, 
          errcolor) 

    plotter.value_label = "Percentage" 

    if ax is None: 
     ax = plt.gca() 

    plotter.plot(ax, kwargs) 
    return ax 
+0

Po pierwsze - jest to imponujące! Po drugie - próbowałem go uruchomić, ale dostałem błąd "Dodge" ... 357 "" "Lista centralnych pozycji wykresów, gdy używane jest zagnieżdżanie." "" 358 n_levels = len (self.hue_names) -> 359 jeśli self.dodge: 360 each_width = self.width/n_levels 361 odsunięć = np.linspace (0, self.Szerokość - each_width, n_levels) AttributeError: obiekt „_BarPlotter” nie ma atrybutu „Dodge” W Seaborn dokumentów: Dodge: BOOL Gdy stosuje odcień zagnieżdżania czy elementy powinny być przesunięte wzdłuż osi jakościowych. – Paul

+0

Używam polecenia Hue (prawdopodobnie przyczyna), ponieważ mam grupy wiekowe dla odcieni i chcę, aby były w porządku rosnącym. Seaborn Docs for Dodge: https://seaborn.pydata.org/generated/seaborn.boxplot.html – Paul

+0

Oto zdjęcie komunikatu o błędzie ... spróbuję dodać "unik" do swojej odpowiedzi. https://ibb.co/m1Wp06 – Paul

Powiązane problemy