2016-01-05 16 views
13

Tôi đã tự hỏi liệu có thể tạo một số đếm Seaborn, nhưng thay vì đếm thực tế trên trục y, hiển thị tần số tương đối (phần trăm) trong nhóm của nó (như được chỉ định với tham số hue).Số lượng Seaborn bình thường với trục y chuẩn hóa trên một nhóm

tôi loại cố định này với các phương pháp sau đây, nhưng tôi không thể tưởng tượng đây là cách tiếp cận đơn giản nhất:

# 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 

Kết quả:

Percentage plot with seaborn

Tôi đang sử dụng tốt bộ dữ liệu người lớn đã biết từ UCI machine learning repository. Các dataframe gấu trúc được tạo ra như thế này:

# 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 là loại liên quan, nhưng không tận dụng các tham số hue. Và trong trường hợp của tôi, tôi không thể thay đổi nhãn trên trục y, vì chiều cao của thanh phải phụ thuộc vào nhóm.

+1

Bạn có muốn chia sẻ dữ liệu cho http://stackoverflow.com/help/mcve không? – Stefan

+0

Không sao cả! Tôi đã thêm liên kết và một số mã ví dụ. –

+0

Tôi đã phải làm điều tương tự như bạn, và tôi đã làm điều tương tự như bạn để đạt được những gì tôi muốn (nhận được số lượng, làm cho df mới, đầu ra như barplot). Vì vậy, tôi vẫn quan tâm nếu có một cách tốt hơn để làm điều này. – spacetyper

Trả lời

3

Tôi có thể bị nhầm lẫn. Sự khác biệt giữa đầu ra của bạn và đầu ra của

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 

là, dường như với tôi, chỉ thứ tự của các cột.

enter image description here

Và bạn dường như quan tâm về điều đó, kể từ khi bạn vượt qua sort=False. Nhưng sau đó, trong mã của bạn, thứ tự được xác định duy nhất một cách ngẫu nhiên (và thứ tự mà từ điển được lặp lại ngay cả những thay đổi từ chạy để chạy với Python 3.5).

3

Nó boggled tâm trí của tôi rằng Seaborn không cung cấp bất cứ điều gì như thế này ra khỏi hộp.

Tuy nhiên, thật dễ dàng để tinh chỉnh mã nguồn để nhận được những gì bạn muốn. Mã sau đây, với hàm "tỷ lệ phần trăm (x, màu sắc, dữ liệu)" hoạt động giống như sns.countplot, nhưng chỉ định mỗi thanh cho mỗi nhóm (tức là chia giá trị của mỗi thanh màu xanh lá cây cho tổng của tất cả các thanh màu xanh lá cây)

trong thực tế, nó quay này (khó để giải thích vì N khác nhau của Apple so với Android): sns.countplot vào này (định chuẩn để thanh phản ánh tỷ lệ tổng cho Apple, vs Android): Percentageplot

Hope this helps !!

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

Đầu tiên - điều này rất ấn tượng! Thứ hai - Tôi đã cố gắng chạy nó nhưng có một lỗi "Dodge" ... 357 "" "Một danh sách các vị trí trung tâm cho các ô khi làm tổ màu được sử dụng." "" 358 n_levels = len (self.hue_names) -> 359 nếu self.dodge: 360 each_width = self.width/n_levels 361 offsets = np.linspace (0, tự.chiều rộng - each_width, n_levels) AttributeError: đối tượng '_BarPlotter' không có thuộc tính 'né tránh' Trong sanh ở biển Documents: né tránh: bool Khi sắc làm tổ được sử dụng, cho dù các yếu tố nên được chuyển dọc theo trục phân loại. – Paul

+0

Tôi đang sử dụng Đơn hàng Huế (có thể là nguyên nhân) vì tôi có các nhóm tuổi cho màu sắc và muốn chúng theo thứ tự tăng dần. Tài liệu Seaborn cho Dodge: https://seaborn.pydata.org/generated/seaborn.boxplot.html – Paul

+0

Đây là hình ảnh của thông báo lỗi ... sẽ tìm cách thêm 'dodge' vào câu trả lời của bạn. https://ibb.co/m1Wp06 – Paul

Các vấn đề liên quan