オヌプン機械孊習コヌス。 トピック2Pythonデヌタの芖芚化


2番目のレッスンでは、Pythonでのデヌタの芖芚化に焊点を圓おたす。 たず、SeabornおよびPlotlyラむブラリの䞻なメ゜ッドを芋おから、 最初の蚘事でおなじみの通信事業者クラむアントの流出に関するデヌタセットを分析し、t-SNEアルゎリズムを䜿甚しおn次元空間を芗き蟌みたす。 たた、オヌプンコヌスの2回目の立ち䞊げ2017幎9月から11月の䞀環ずしお、この蚘事に基づく講矩のビデオ録画もありたす。


UPD珟圚、コヌスは英語で、 mlcourse.aiずいうブランド名で、Medium に関する蚘事 、Kaggle Dataset およびGitHubに関する資料がありたす 。


これで、蚘事はかなり長くなりたす。 準備はいい 行こう



この蚘事の抂芁



SeabornおよびPlotlyの基本的な方法のデモンストレヌション


最初は、い぀ものように、環境を蚭定したす。必芁なすべおのラむブラリをむンポヌトし、デフォルトのデフォルト画像衚瀺を蚭定したす。


#   Anaconda import warnings warnings.simplefilter('ignore') #      jupyter'e %matplotlib inline import seaborn as sns import matplotlib.pyplot as plt #  svg    %config InlineBackend.figure_format = 'svg' #    from pylab import rcParams rcParams['figure.figsize'] = 8, 5 import pandas as pd 

その埌、 DataFrame䜜業するデヌタをロヌドしたす。 䟋ずしお、 Kaggle Datasetsからビデオゲヌムの販売デヌタず評䟡を遞択したした。


 df = pd.read_csv('../../data/video_games_sales.csv') df.info() 

 <class 'pandas.core.frame.DataFrame'> RangeIndex: 16719 entries, 0 to 16718 Data columns (total 16 columns): Name 16717 non-null object Platform 16719 non-null object Year_of_Release 16450 non-null float64 Genre 16717 non-null object Publisher 16665 non-null object NA_Sales 16719 non-null float64 EU_Sales 16719 non-null float64 JP_Sales 16719 non-null float64 Other_Sales 16719 non-null float64 Global_Sales 16719 non-null float64 Critic_Score 8137 non-null float64 Critic_Count 8137 non-null float64 User_Score 10015 non-null object User_Count 7590 non-null float64 Developer 10096 non-null object Rating 9950 non-null object dtypes: float64(9), object(7) memory usage: 2.0+ MB 

pandas object考えるいく぀かの兆候object 、明瀺的にfloatたたはintキャストされたす。


 df['User_Score'] = df.User_Score.astype('float64') df['Year_of_Release'] = df.Year_of_Release.astype('int64') df['User_Count'] = df.User_Count.astype('int64') df['Critic_Count'] = df.Critic_Count.astype('int64') 

デヌタはすべおのゲヌムのものではないため、 dropnaメ゜ッドを䜿甚しお、ギャップのないレコヌドのみを残したしょう。


 df = df.dropna() print(df.shape) 

 (6825, 16) 

テヌブルには6825個のオブゞェクトがあり、16個のサむンがありたす。 headメ゜ッドを䜿甚しお最初のいく぀かの゚ントリを芋お、すべおが正しく解析されるこずを確認したしょう。 䟿宜䞊、将来䜿甚する暙識のみを残したした。


 useful_cols = ['Name', 'Platform', 'Year_of_Release', 'Genre', 'Global_Sales', 'Critic_Score', 'Critic_Count', 'User_Score', 'User_Count', 'Rating' ] df[useful_cols].head() 

img


seabornおよびplotlyのメ゜ッドに移る前に、 seabornからデヌタを芖芚化する最も簡単で䟿利な方法に぀いお説明しpandas DataFrame関数を䜿甚しplot 。
たずえば、幎に応じおさたざたな囜でビデオゲヌムの販売スケゞュヌルを䜜成したす。 最初に、必芁な列のみをフィルタヌで陀倖し、幎DataFrameの総売䞊を蚈算し、結果のDataFrameパラメヌタヌなしでplot関数を呌び出したす。


 sales_df = df[[x for x in df.columns if 'Sales' in x] + ['Year_of_Release']] sales_df.groupby('Year_of_Release').sum().plot() 

pandasのplot関数の実装は、 matplotlibラむブラリに基づいおいたす。


img


kindパラメヌタヌを䜿甚しお、チャヌトのタむプをbar chartなどに倉曎できbar chart 。 Matplotlibは、非垞に柔軟なグラフィックのカスタマむズを可胜にしたす。 グラフでは、ほずんど䜕でも倉曎できたすが、ドキュメントをざっず調べお必芁なパラメヌタヌを芋぀ける必芁がありたす。 たずえば、 rotパラメヌタヌは、 x軞に察するラベルの募配を担圓したす。


 sales_df.groupby('Year_of_Release').sum().plot(kind='bar', rot=45) 

img


シヌボヌン


それでは、 seabornラむブラリヌに移りたしょう。 Seabornは、本質的にmatplotlibラむブラリに基づいた高レベルAPIです。 Seabornは、より適切なデフォルトのチャヌト蚭定が含たれおいたす。 たた、ラむブラリには、 matplotlibでは倚くのコヌドを必芁ずする非垞に耇雑なタむプの芖芚化がありたす。


最初のそのような「耇雑な」タむプのグラフpair plot  scatter plot matrix をscatter plot matrixたしょう。 この芖芚化は、さたざたな機胜がどのように関連しおいるかを1぀の図で芋るのに圹立ちたす。


 cols = ['Global_Sales', 'Critic_Score', 'Critic_Count', 'User_Score', 'User_Count'] sns_plot = sns.pairplot(df[cols]) sns_plot.savefig('pairplot.png') 

ご芧のずおり、属性の分垃のヒストグラムはグラフマトリックスの察角線䞊にありたす。 残りのグラフは、察応する蚘号のペアの通垞のscatter plotsです。


グラフをファむルに保存するには、 savefigメ゜ッドを䜿甚したす。


img


seaborn 、 dist plot分垃を構築するこずもできたす。 䟋ずしお、批評家の評䟡Critic_Score分垃を芋おみたしょう。 デフォルトでは、グラフにはヒストグラムずカヌネル密床の掚定が衚瀺されたす。


 sns.distplot(df.Critic_Score) 

img


2぀の数倀蚘号の関係を詳しく芋るために、 joint plotもありjoint plotこれはscatter plotずhistogramハむブリッドです。 Critic_Score評論家のCritic_ScoreずUser_Scoreナヌザヌの評䟡がどのようにCritic_Scoreしおいるか芋おみたしょう。


img


別の䟿利な皮類のチャヌトはbox plotです。 䞊䜍5倧ゲヌムプラットフォヌムの批評家のゲヌム評䟡を比范したしょう。


 top_platforms = df.Platform.value_counts().sort_values(ascending = False).head(5).index.values sns.boxplot(y="Platform", x="Critic_Score", data=df[df.Platform.isin(top_platforms)], orient="h") 

img


box plot理解方法に぀いおもう少し詳しく説明する䟡倀があるず思いbox plot 。 Box plotは、ボックス box plotず呌ばれる理由、アンテナ、ポむントで構成されたす。 ボックスには、分垃の四分䜍範囲、぀たりそれぞれ25 Q1 および75 Q3 パヌセンタむルが衚瀺されたす。 ボックス内のダッシュは、分垃の䞭倮倀を瀺したす。
ボックスが敎理されたら、口ひげに移りたしょう。 ひげは、倖れ倀を陀く点の散垃党䜓、぀たり、間隔(Q1 - 1.5*IQR, Q3 + 1.5*IQR)に入る最小倀ず最倧倀を衚したす。ここで、 IQR = Q3 - Q1は四分䜍範囲です。 グラフ䞊のドットはoutliers瀺したす-グラフの口ひげで指定された倀の範囲に収たらない倀。


理解するために、䞀床芋たほうが良いので、ここにりィキペディアの写真もありたす
img


たた、別のタむプのグラフこの蚘事で怜蚎する最埌のグラフはheat mapです。 Heat map䜿甚するず、2぀のカテゎリに応じた数倀特性の分垃を確認できたす。 ゞャンルずゲヌムプラットフォヌム別にゲヌムの総売䞊を芖芚化したす。


 platform_genre_sales = df.pivot_table( index='Platform', columns='Genre', values='Global_Sales', aggfunc=sum).fillna(0).applymap(float) sns.heatmap(platform_genre_sales, annot=True, fmt=".1f", linewidths=.5) 

img


プロットリヌ


matplotlibラむブラリに基づいお芖芚化を怜蚎したした。 ただし、これはpythonでグラフを䜜成するための唯䞀のオプションではありたせん。 たた、 plotlyラむブラリに぀いおもplotlyしたしょう。 Plotlyは、javascriptコヌドを掘り䞋げるこずなくjupyter.notebook'eでむンタラクティブなグラフィックを構築できるオヌプン゜ヌスラむブラリです。


むンタラクティブグラフの利点は、マりスをポむントしたずきに正確な数倀を衚瀺したり、芖芚化で重芁でないシリヌズを非衚瀺にしたり、グラフの特定のセクションを拡倧したりできるこずです。


䜜業を開始する前に、必芁なすべおのモゞュヌルをむンポヌトし、 init_notebook_modeを䜿甚しおinit_notebook_modeを初期化したす。


 from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot import plotly import plotly.graph_objs as go init_notebook_mode(connected=True) 

たず、リリヌスされたゲヌムの数ずその幎ごずの売り䞊げのダむナミクスを含むline plotを䜜成したす。


 #          years_df = df.groupby('Year_of_Release')[['Global_Sales']].sum().join( df.groupby('Year_of_Release')[['Name']].count() ) years_df.columns = ['Global_Sales', 'Number_of_Games'] #       trace0 = go.Scatter( x=years_df.index, y=years_df.Global_Sales, name='Global Sales' ) #       trace1 = go.Scatter( x=years_df.index, y=years_df.Number_of_Games, name='Number of games released' ) #      title   layout data = [trace0, trace1] layout = {'title': 'Statistics of video games'} # c  Figure    fig = go.Figure(data=data, layout=layout) iplot(fig, show_link=False) 

plotlyは、 Figureオブゞェクトの芖芚化が構築されたす。これは、デヌタラむブラリ内でtracesず呌ばれる行の配列ず、 layoutオブゞェクトが担圓するデザむン/スタむルで構成されlayout 。 単玔な堎合、 iplot関数をiplot配列からのみ呌び出すこずができたす。


show_linkパラメヌタヌshow_link 、チャヌト䞊のplot.lyオンラむンプラットフォヌムぞのリンクを担圓したす。 通垞、この機胜は必芁ないため、偶発的なクリックを防ぐために非衚瀺にするこずを奜みたす。


img


チャヌトをすぐにhtmlファむルずしお保存できたす。


 plotly.offline.plot(fig, filename='years_stats.html', show_link=False) 

たた、リリヌスされたゲヌムの数ず総収益によっお蚈算されたゲヌムプラットフォヌムの垂堎シェアも芋おみたしょう。 これを行うには、 bar chart䜜成しbar chart 。


 #         platforms_df = df.groupby('Platform')[['Global_Sales']].sum().join( df.groupby('Platform')[['Name']].count() ) platforms_df.columns = ['Global_Sales', 'Number_of_Games'] platforms_df.sort_values('Global_Sales', ascending=False, inplace=True) #  traces   trace0 = go.Bar( x=platforms_df.index, y=platforms_df.Global_Sales, name='Global Sales' ) trace1 = go.Bar( x=platforms_df.index, y=platforms_df.Number_of_Games, name='Number of games released' ) #       title     x  layout data = [trace0, trace1] layout = {'title': 'Share of platforms', 'xaxis': {'title': 'platform'}} #   Figure    fig = go.Figure(data=data, layout=layout) iplot(fig, show_link=False) 

img


plotly 、 box plotも䜜成できbox plot 。 ゲヌムのゞャンルに応じた批評家の評䟡の分垃を考慮しおください。


 #  Box trace       data = [] for genre in df.Genre.unique(): data.append( go.Box(y=df[df.Genre==genre].Critic_Score, name=genre) ) #   iplot(data, show_link = False) 

img


plotlyを䜿甚するず、他の皮類の芖芚化を構築できたす。 グラフはデフォルトの蚭定でかなりいいです。 ただし、ラむブラリを䜿甚するず、色、フォント、眲名、泚釈など、さたざたな芖芚化オプションを柔軟に構成できたす。


芖芚デヌタ分析の䟋


テレコムオペレヌタヌクラむアントの流出に関する最初の蚘事で私たちがよく知っおいるDataFrame読み蟌みたす。


 df = pd.read_csv('../../data/telecom_churn.csv') 

すべおが正垞ず芋なされたかどうかを確認したしょう-最初の5行を芋おみたしょう headメ゜ッド。


 df.head() 



行クラむアントず列特性の数


 df.shape 

 (3333, 20) 

兆候を芋お、それらのいずれにもギャップがないこずを確認したしょう-どこでも3333゚ントリ。


 df.info() 

 <class 'pandas.core.frame.DataFrame'> RangeIndex: 3333 entries, 0 to 3332 Data columns (total 20 columns): State 3333 non-null object Account length 3333 non-null int64 Area code 3333 non-null int64 International plan 3333 non-null object Voice mail plan 3333 non-null object Number vmail messages 3333 non-null int64 Total day minutes 3333 non-null float64 Total day calls 3333 non-null int64 Total day charge 3333 non-null float64 Total eve minutes 3333 non-null float64 Total eve calls 3333 non-null int64 Total eve charge 3333 non-null float64 Total night minutes 3333 non-null float64 Total night calls 3333 non-null int64 Total night charge 3333 non-null float64 Total intl minutes 3333 non-null float64 Total intl calls 3333 non-null int64 Total intl charge 3333 non-null float64 Customer service calls 3333 non-null int64 Churn 3333 non-null bool dtypes: bool(1), float64(8), int64(8), object(3) memory usage: 498.1+ KB 

暙識の説明
圹職説明皮類
郜道府県州の手玙コヌドカテゎリヌ
アカりントの長さ䌚瀟が顧客にサヌビスを提䟛しおいる期間定量的
垂倖局番電話番号のプレフィックス定量的
囜際蚈画囜際ロヌミング接続枈み/未接続バむナリ
ボむスメヌルプランボむスメヌル接続枈み/未接続バむナリ
vmailメッセヌゞの数音声メッセヌゞの数定量的
総日分日䞭の䌚話の合蚈時間定量的
合蚈日通話日䞭の通話の総数定量的
合蚈日料金日䞭のサヌビスの支払い総額定量的
合蚈前倜倕方の合蚈䌚話時間定量的
総通話数合蚈倜の呌び出し定量的
前倜料金倜の合蚈サヌビス料定量的
総倜数倜の䌚話の合蚈時間定量的
合蚈倜間通話倜の合蚈通話数定量的
合蚈宿泊料金倜のサヌビスの合蚈支払い額定量的
合蚈囜際分囜際通話の合蚈時間定量的
合蚈囜際電話合蚈囜際電話定量的
合蚈料金囜際通話料金の合蚈定量的
カスタマヌサヌビスコヌルサヌビスセンタヌぞの呌び出し回数定量的

タヌゲット倉数 チャヌン -流出の兆候、バむナリ1-クラむアントの損倱、぀たり流出。 次に、この機胜を残りから予枬するモデルを構築したす。これがタヌゲットず呌ばれる理由です。


タヌゲットクラスの分垃、぀たり顧客の流出を芋おみたしょう。


 df['Churn'].value_counts() 

 False 2850 True 483 Name: Churn, dtype: int64 

 df['Churn'].value_counts().plot(kind='bar', label='Churn') plt.legend() plt.title('  '); 



次のグルヌプのサむンを区別したす チャヌンを陀くすべお。



量的圢質の盞関関係を芋おみたしょう。 色付きの盞関行列は、 総日料金などの兆候が話された分 総日分 によっお考慮されるこずを瀺しおいたす。 ぀たり、4぀の暙識は捚おるこずができたすが、有甚な情報は䌝達されたせん。


 corr_matrix = df.drop(['State', 'International plan', 'Voice mail plan', 'Area code'], axis=1).corr() 

 sns.heatmap(corr_matrix); 



次に、関心のあるすべおの量的特性の分垃を芋おみたしょう。 バむナリ/カテゎリ/順序の蚘号を個別に芋おいきたす。


 features = list(set(df.columns) - set(['State', 'International plan', 'Voice mail plan', 'Area code', 'Total day charge', 'Total eve charge', 'Total night charge', 'Total intl charge', 'Churn'])) df[features].hist(figsize=(20,12)); 



ほずんどのサむンは正垞に分垃しおいるこずがわかりたす。 䟋倖は、カスタマヌサヌビスセンタヌぞのコヌルの数ここではポア゜ン分垃の方が適しおいたすおよびボむスメッセヌゞの数  番号vmailメッセヌゞ 、れロでピヌク、぀たりボむスメヌルがない人です。 囜際電話の数の分垃 Total intl calls も偏っおいたす。


暙識の分垃が䞻な察角線䞊に描かれ、暙識のペアの散垃図が䞻な察角線の倖偎に描かれるような画像を構築するこずは䟝然ずしお有甚です。 これにより、いく぀かの結論に至るこずがありたすが、この堎合、驚くこずなくすべおがほが明確です。


 sns.pairplot(df[features + ['Churn']], hue='Churn'); 



次に、兆候がどのようにタヌゲットに関連付けられおいるかを確認したす-流出。


忠実な顧客ず去った顧客の間の2぀のグルヌプでの量的属性の分垃の統蚈を蚘述する箱ひげ図を䜜成したしょう。


 fig, axes = plt.subplots(nrows=3, ncols=4, figsize=(16, 10)) for idx, feat in enumerate(features): sns.boxplot(x='Churn', y=feat, data=df, ax=axes[idx / 4, idx % 4]) axes[idx / 4, idx % 4].legend() axes[idx / 4, idx % 4].set_xlabel('Churn') axes[idx / 4, idx % 4].set_ylabel(feat); 



䞀芋するず、「 合蚈日数」 、「 カスタマヌサヌビスコヌル」 、および「 vmailメッセヌゞの数」の兆候が最も倧きく異なりたす 。 続いお、ランダムフォレストたたは募配ブヌスティングを䜿甚しお分類問題の特城の重芁性を刀断する方法を孊習したす。最初の2぀は流出を予枬するための非垞に重芁な特城であるこずがわかりたす。


忠実な/出発した人の間で日䞭に話された分数の分垃で写真を別々に芋おみたしょう。 巊偎には私たちにずっお銎染みのある箱ひげ図があり、右偎には2぀のグルヌプの数倀蚘号の分垃の平滑化されたヒストグラムがありたすきれいな画像ではなく、箱ひげ図からすべおがはっきりしおいたす。


興味深い芳察平均しお、去った顧客はコミュニケヌションをより倚く䜿甚したす。 おそらく圌らは関皎に䞍満を抱いおおり、流出ず戊うための察策の1぀は関皎率モバむル通信のコストを䞋げるこずでしょう。 しかし、このような措眮が本圓に正圓化されるかどうか、䌁業は远加の経枈分析を行う必芁がありたす。


 _, axes = plt.subplots(1, 2, sharey=True, figsize=(16,6)) sns.boxplot(x='Churn', y='Total day minutes', data=df, ax=axes[0]); sns.violinplot(x='Churn', y='Total day minutes', data=df, ax=axes[1]); 



次に、サヌビスセンタヌぞの呌び出し数の分垃を瀺したす最初の蚘事でそのような図を䜜成したした。 属性の䞀意の倀は倚くありたせん属性は量的敎数たたは順序ず芋なすこずができたす countplotを䜿甚しお分垃をより明確に衚したす。 芳察流出の割合は、サヌビスセンタヌぞの4回の呌び出しから倧きく増加したす。


 sns.countplot(x='Customer service calls', hue='Churn', data=df); 



ここで、 囜際蚈画およびボむスメヌル蚈画のバむナリサむンず流出ずの関係を芋おみたしょう。 芳察 ロヌミングが接続されおいる堎合、流出率ははるかに高くなりたす。 囜際ロヌミングは匷力な兆候です。 これは、ボむスメヌルには圓おはたりたせん。


 _, axes = plt.subplots(1, 2, sharey=True, figsize=(16,6)) sns.countplot(x='International plan', hue='Churn', data=df, ax=axes[0]); sns.countplot(x='Voice mail plan', hue='Churn', data=df, ax=axes[1]); 



最埌に、カテゎリ属性Stateがどのように流出に関連付けられおいるかを芋おみたしょう。 ナニヌクな州の数は非垞に倚いため、圌ず䞀緒に仕事をするのはそれほど快適ではありたせん-51。最初にサマリヌプレヌトを䜜成するか、各州の流出の割合を蚈算できたす。 ただし、各州のデヌタは個別に十分ではないため各州には3〜17人の出発顧客しかありたせん、したがっお、おそらく再蚓緎のリスクがあるため、 State属性を分類モデルに远加すべきではありたせんただし、 クロス怜蚌に぀いおはこれをチェックしたす、ご期埅ください。


各状態の流出率


 df.groupby(['State'])['Churn'].agg([np.mean]).sort_values(by='mean', ascending=False).T 




ニュヌゞャヌゞヌ州ずカリフォルニア州では流出の割合が25を超えおいたすが、ハワむずアラスカでは5未満であるこずがわかりたす。 しかし、これらの結論はあたりにも控えめな統蚈に基づいおおり、おそらくこれらは利甚可胜なデヌタの特城にすぎたせんここでは、MatthewsずCramerの盞関関係に関する仮説を確認できたすが、これはこの蚘事の範囲倖です。


t-SNEを䜿甚したn次元空間の芗き芋


同じ流出デヌタのt-SNE衚珟を䜜成したす。 メ゜ッドの名前は耇雑です-t分垃のStohastic Neighbor Embedding、数孊もクヌルですそしお、私たちはそれには入りたせんが、 これは D.ヒントンずJMLRの倧孊院生によるオリゞナルの蚘事です。倚次元の特城空間から平面たでたたは3Dですが、ほずんどの堎合2Dが遞択されたす、平面䞊で互いに離れおいるポむントも遠くにあり、近いポむントも近くに衚瀺されたす。 ぀たり、近傍埋め蟌みは、近傍が保存されおいるデヌタの新しい衚珟の䞀皮の怜玢です。


いく぀かの詳现状態ず流出の兆候をpd.factorizeたす。バむナリのYes / No兆候は数字に倉換されたす pd.factorize 。 たた、遞択をスケヌリングする必芁がありたす-各フィヌチャから平均を枛算し、暙準偏差で陀算したす。これはStandardScalerによっお行われたす。


 from sklearn.manifold import TSNE from sklearn.preprocessing import StandardScaler 

 #     ,   X = df.drop(['Churn', 'State'], axis=1) X['International plan'] = pd.factorize(X['International plan'])[0] X['Voice mail plan'] = pd.factorize(X['Voice mail plan'])[0] scaler = StandardScaler() X_scaled = scaler.fit_transform(X) 

 %%time tsne = TSNE(random_state=17) tsne_representation = tsne.fit_transform(X_scaled) 

 CPU times: user 20 s, sys: 2.41 s, total: 22.4 s Wall time: 21.9 s 

 plt.scatter(tsne_representation[:, 0], tsne_representation[:, 1]); 



結果の流出デヌタのt-SNE衚珟を色付けしたす青-忠実、オレンゞ-出発した顧客。


 plt.scatter(tsne_representation[:, 0], tsne_representation[:, 1], c=df['Churn'].map({0: 'blue', 1: 'orange'})); 



出発した顧客は、属性空間の䞀郚の領域で䞻に「グルヌプ化」されおいるこずがわかりたす。


画像をよりよく理解するために、ロヌミングずボむスメヌルなど、バむナリ蚘号の残りの郚分で色を付けるこずもできたす。 青い領域は、このバむナリ機胜を持぀オブゞェクトに察応しおいたす。


 _, axes = plt.subplots(1, 2, sharey=True, figsize=(16,6)) axes[0].scatter(tsne_representation[:, 0], tsne_representation[:, 1], c=df['International plan'].map({'Yes': 'blue', 'No': 'orange'})); axes[1].scatter(tsne_representation[:, 0], tsne_representation[:, 1], c=df['Voice mail plan'].map({'Yes': 'blue', 'No': 'orange'})); axes[0].set_title('International plan'); axes[1].set_title('Voice mail plan'); 



たずえば、ロヌミングがオフになっおいるがボむスメヌルがない状態で、倚くの退去した顧客が巊の人々の集たりにグルヌプ化されおいるこずは明らかです。


最埌に、t-SNEの欠点に泚意したすはい、別の蚘事を曞くこずをお勧めしたす。



. t-SNE ( , ), .




№ 2


, .


– . - ( ).




yorko ( ).

Source: https://habr.com/ru/post/J323210/


All Articles