0
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Pythonのダッシュ(Dash)ライブラリ:データ可視化の新時代

Posted at

はじめに

ダッシュ(Dash)は、Pythonを使用してインタラクティブなウェブベースのデータ可視化アプリケーションを作成するための強力なオープンソースフレームワークです。データサイエンティストやアナリストにとって、高度なウェブ開発の知識なしに分析ダッシュボードを構築できる革新的なツールです。FlaskウェブサーバーとReact.jsユーザーインターフェース、そしてPlotly.jsグラフィングライブラリを組み合わせることで、ダッシュは美しく機能的なアプリケーションを簡単に作成することを可能にします。

第1章:ダッシュのインストールと基本設定

ダッシュを使い始めるには、まずPythonの環境にダッシュをインストールする必要があります。コマンドラインを開き、以下のコマンドを実行してダッシュとその依存関係をインストールしましょう。

pip install dash
pip install pandas
pip install plotly

インストールが完了したら、新しいPythonファイルを作成し、必要なライブラリをインポートします。

import dash
from dash import dcc, html
from dash.dependencies import Input, Output
import plotly.express as px
import pandas as pd

# ダッシュアプリケーションの初期化
app = dash.Dash(__name__)

# アプリケーションのレイアウトを定義
app.layout = html.Div([
    html.H1('私の最初のダッシュアプリ'),
    dcc.Graph(id='example-graph')
])

# サーバーの起動
if __name__ == '__main__':
    app.run_server(debug=True)

この基本的なセットアップにより、「私の最初のダッシュアプリ」というタイトルと空のグラフを含む簡単なウェブページが作成されます。

第2章:ダッシュレイアウトの基礎

ダッシュアプリケーションのレイアウトは、ウェブページの構造と外観を定義します。HTML要素とダッシュコアコンポーネント(DCC)を組み合わせて、インタラクティブなダッシュボードを作成できます。以下は、より複雑なレイアウトの例です。

import dash
from dash import dcc, html

app = dash.Dash(__name__)

app.layout = html.Div([
    html.H1('日本の気象データダッシュボード'),
    html.Div([
        html.Label('都市を選択:'),
        dcc.Dropdown(
            id='city-dropdown',
            options=[
                {'label': '東京', 'value': 'tokyo'},
                {'label': '大阪', 'value': 'osaka'},
                {'label': '札幌', 'value': 'sapporo'}
            ],
            value='tokyo'
        )
    ]),
    dcc.Graph(id='temperature-graph'),
    dcc.Graph(id='precipitation-graph')
])

if __name__ == '__main__':
    app.run_server(debug=True)

このレイアウトでは、ヘッダー、ドロップダウンメニュー、そして2つのグラフプレースホルダーを含むダッシュボードを作成しています。ユーザーは都市を選択でき、それに応じてグラフが更新されるようになります。

第3章:データの読み込みと前処理

ダッシュアプリケーションの心臓部は、表示するデータです。Pandasを使用してCSVファイルからデータを読み込み、必要に応じて前処理を行います。以下は、架空の気象データを読み込む例です。

import pandas as pd

# CSVファイルからデータを読み込む
df = pd.read_csv('japan_weather_data.csv')

# 日付列を日付型に変換
df['date'] = pd.to_datetime(df['date'])

# 都市ごとにデータをグループ化
city_data = {city: df[df['city'] == city] for city in df['city'].unique()}

print(df.head())
print(city_data.keys())

このコードでは、CSVファイルからデータを読み込み、日付列を適切な形式に変換し、都市ごとにデータをグループ化しています。これにより、後でグラフを作成する際に簡単にデータにアクセスできるようになります。

第4章:基本的なグラフの作成

Plotly Expressを使用して、ダッシュアプリケーション内に魅力的なグラフを作成できます。以下は、温度データを表示する折れ線グラフを作成する例です。

import plotly.express as px

def create_temperature_graph(city):
    fig = px.line(city_data[city], x='date', y='temperature',
                  title=f'{city}の気温推移',
                  labels={'temperature': '気温 (°C)', 'date': '日付'})
    return fig

app.layout = html.Div([
    html.H1('日本の気象データダッシュボード'),
    dcc.Dropdown(
        id='city-dropdown',
        options=[{'label': city, 'value': city} for city in city_data.keys()],
        value='tokyo'
    ),
    dcc.Graph(id='temperature-graph')
])

@app.callback(
    Output('temperature-graph', 'figure'),
    Input('city-dropdown', 'value')
)
def update_graph(selected_city):
    return create_temperature_graph(selected_city)

if __name__ == '__main__':
    app.run_server(debug=True)

このコードでは、選択された都市の気温データを表示する折れ線グラフを作成しています。ドロップダウンメニューで都市を選択すると、グラフが自動的に更新されます。

第5章:インタラクティブ性の追加

ダッシュの強みの一つは、簡単にインタラクティブな要素を追加できることです。コールバックを使用して、ユーザーの入力に応じてグラフやその他の要素を動的に更新できます。以下は、日付範囲選択機能を追加する例です。

app.layout = html.Div([
    html.H1('日本の気象データダッシュボード'),
    dcc.Dropdown(
        id='city-dropdown',
        options=[{'label': city, 'value': city} for city in city_data.keys()],
        value='tokyo'
    ),
    dcc.DatePickerRange(
        id='date-range',
        start_date=df['date'].min(),
        end_date=df['date'].max(),
        display_format='YYYY-MM-DD'
    ),
    dcc.Graph(id='temperature-graph')
])

@app.callback(
    Output('temperature-graph', 'figure'),
    Input('city-dropdown', 'value'),
    Input('date-range', 'start_date'),
    Input('date-range', 'end_date')
)
def update_graph(selected_city, start_date, end_date):
    filtered_df = city_data[selected_city]
    filtered_df = filtered_df[(filtered_df['date'] >= start_date) & (filtered_df['date'] <= end_date)]
    
    fig = px.line(filtered_df, x='date', y='temperature',
                  title=f'{selected_city}の気温推移',
                  labels={'temperature': '気温 (°C)', 'date': '日付'})
    return fig

この更新により、ユーザーは都市を選択し、さらに表示する日付範囲を指定できるようになりました。コールバック関数は、これらの入力に基づいてグラフを動的に更新します。

第6章:複数のグラフの組み合わせ

ダッシュボードの情報量を増やすために、複数のグラフを1つのページに組み合わせることができます。以下は、気温と降水量を同時に表示する例です。

app.layout = html.Div([
    html.H1('日本の気象データダッシュボード'),
    dcc.Dropdown(
        id='city-dropdown',
        options=[{'label': city, 'value': city} for city in city_data.keys()],
        value='tokyo'
    ),
    dcc.DatePickerRange(
        id='date-range',
        start_date=df['date'].min(),
        end_date=df['date'].max(),
        display_format='YYYY-MM-DD'
    ),
    html.Div([
        dcc.Graph(id='temperature-graph', style={'width': '50%', 'display': 'inline-block'}),
        dcc.Graph(id='precipitation-graph', style={'width': '50%', 'display': 'inline-block'})
    ])
])

@app.callback(
    Output('temperature-graph', 'figure'),
    Output('precipitation-graph', 'figure'),
    Input('city-dropdown', 'value'),
    Input('date-range', 'start_date'),
    Input('date-range', 'end_date')
)
def update_graphs(selected_city, start_date, end_date):
    filtered_df = city_data[selected_city]
    filtered_df = filtered_df[(filtered_df['date'] >= start_date) & (filtered_df['date'] <= end_date)]
    
    temp_fig = px.line(filtered_df, x='date', y='temperature',
                       title=f'{selected_city}の気温推移',
                       labels={'temperature': '気温 (°C)', 'date': '日付'})
    
    precip_fig = px.bar(filtered_df, x='date', y='precipitation',
                        title=f'{selected_city}の降水量',
                        labels={'precipitation': '降水量 (mm)', 'date': '日付'})
    
    return temp_fig, precip_fig

このコードでは、1つのコールバック関数で2つのグラフを同時に更新しています。気温のグラフは折れ線グラフのままですが、降水量のデータは棒グラフで表示されるようになりました。

第7章:データテーブルの追加

グラフだけでなく、生のデータを表形式で表示することも重要です。ダッシュは、インタラクティブなデータテーブルを簡単に作成できる機能を提供しています。

from dash import dash_table

app.layout = html.Div([
    html.H1('日本の気象データダッシュボード'),
    dcc.Dropdown(
        id='city-dropdown',
        options=[{'label': city, 'value': city} for city in city_data.keys()],
        value='tokyo'
    ),
    dcc.DatePickerRange(
        id='date-range',
        start_date=df['date'].min(),
        end_date=df['date'].max(),
        display_format='YYYY-MM-DD'
    ),
    html.Div([
        dcc.Graph(id='temperature-graph', style={'width': '50%', 'display': 'inline-block'}),
        dcc.Graph(id='precipitation-graph', style={'width': '50%', 'display': 'inline-block'})
    ]),
    dash_table.DataTable(
        id='data-table',
        columns=[{"name": i, "id": i} for i in df.columns],
        page_size=10,
        style_table={'overflowX': 'auto'}
    )
])

@app.callback(
    Output('temperature-graph', 'figure'),
    Output('precipitation-graph', 'figure'),
    Output('data-table', 'data'),
    Input('city-dropdown', 'value'),
    Input('date-range', 'start_date'),
    Input('date-range', 'end_date')
)
def update_dashboard(selected_city, start_date, end_date):
    filtered_df = city_data[selected_city]
    filtered_df = filtered_df[(filtered_df['date'] >= start_date) & (filtered_df['date'] <= end_date)]
    
    temp_fig = px.line(filtered_df, x='date', y='temperature',
                       title=f'{selected_city}の気温推移',
                       labels={'temperature': '気温 (°C)', 'date': '日付'})
    
    precip_fig = px.bar(filtered_df, x='date', y='precipitation',
                        title=f'{selected_city}の降水量',
                        labels={'precipitation': '降水量 (mm)', 'date': '日付'})
    
    return temp_fig, precip_fig, filtered_df.to_dict('records')

このコードでは、グラフの下にデータテーブルを追加しています。テーブルは選択された都市と日付範囲に基づいて動的に更新されます。

第8章:スタイリングとレイアウトの改善

ダッシュボードの見た目を改善するために、CSSスタイリングを適用できます。以下は、レイアウトとスタイルを改善した例です。

external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']

app = dash.Dash(__name__, external_stylesheets=external_stylesheets)

app.layout = html.Div([
    html.H1('日本の気象データダッシュボード', style={'textAlign': 'center', 'color': '#2c3e50'}),
    html.Div([
        html.Div([
            html.Label('都市を選択:'),
            dcc.Dropdown(
                id='city-dropdown',
                options=[{'label': city, 'value': city} for city in city_data.keys()],
                value='tokyo'
            )
        ], style={'width': '48%', 'display': 'inline-block'}),
        html.Div([
            html.Label('日付範囲:'),
            dcc.DatePickerRange(
                id='date-range',
                start_date=df['date'].min(),
                end_date=df['date'].max(),
はい続けさせていただきます

                display_format='YYYY-MM-DD'
            )
        ], style={'width': '48%', 'float': 'right', 'display': 'inline-block'})
    ]),
    html.Div([
        dcc.Graph(id='temperature-graph', style={'width': '48%', 'display': 'inline-block'}),
        dcc.Graph(id='precipitation-graph', style={'width': '48%', 'float': 'right', 'display': 'inline-block'})
    ]),
    dash_table.DataTable(
        id='data-table',
        columns=[{"name": i, "id": i} for i in df.columns],
        page_size=10,
        style_table={'overflowX': 'auto'},
        style_cell={'textAlign': 'left'},
        style_header={
            'backgroundColor': 'rgb(230, 230, 230)',
            'fontWeight': 'bold'
        }
    )
], style={'padding': '20px'})

このコードでは、外部のCSSスタイルシートを使用し、レイアウトの各要素にインラインスタイルを適用しています。これにより、ダッシュボードの見た目が大幅に改善され、より洗練された外観になります。

第9章:高度なグラフ機能:ホバー情報とツールチップ

ユーザーエクスペリエンスを向上させるために、グラフにホバー情報とカスタムツールチップを追加できます。以下は、温度グラフにこれらの機能を追加する例です。

@app.callback(
    Output('temperature-graph', 'figure'),
    Input('city-dropdown', 'value'),
    Input('date-range', 'start_date'),
    Input('date-range', 'end_date')
)
def update_temperature_graph(selected_city, start_date, end_date):
    filtered_df = city_data[selected_city]
    filtered_df = filtered_df[(filtered_df['date'] >= start_date) & (filtered_df['date'] <= end_date)]
    
    fig = px.line(filtered_df, x='date', y='temperature',
                  title=f'{selected_city}の気温推移',
                  labels={'temperature': '気温 (°C)', 'date': '日付'})
    
    fig.update_traces(
        hoverinfo='text',
        hovertemplate='日付: %{x}<br>気温: %{y:.1f}°C<extra></extra>'
    )
    
    fig.update_layout(
        hoverlabel=dict(
            bgcolor="white",
            font_size=16,
            font_family="Rockwell"
        )
    )
    
    return fig

このコードでは、update_tracesupdate_layoutメソッドを使用して、ホバー情報の内容とスタイルをカスタマイズしています。ユーザーがグラフ上にマウスを置くと、日付と気温の詳細情報が表示されます。

第10章:アニメーションの追加

データの時間的変化を視覚的に表現するために、アニメーションを追加することができます。以下は、月ごとの平均気温の変化を示すアニメーショングラフの例です。

import plotly.graph_objects as go

@app.callback(
    Output('animation-graph', 'figure'),
    Input('city-dropdown', 'value')
)
def update_animation(selected_city):
    df = city_data[selected_city]
    df['month'] = df['date'].dt.to_period('M')
    monthly_avg = df.groupby('month').agg({'temperature': 'mean', 'precipitation': 'sum'}).reset_index()
    monthly_avg['month'] = monthly_avg['month'].dt.to_timestamp()

    fig = go.Figure(
        data=[go.Scatter(x=monthly_avg['month'], y=monthly_avg['temperature'], mode='lines+markers')],
        layout=go.Layout(
            title=f'{selected_city}の月平均気温の推移',
            xaxis=dict(title='月'),
            yaxis=dict(title='平均気温 (°C)')
        ),
        frames=[go.Frame(
            data=[go.Scatter(
                x=monthly_avg['month'][:k+1],
                y=monthly_avg['temperature'][:k+1],
                mode='lines+markers'
            )],
            name=str(k)
        ) for k in range(len(monthly_avg))]
    )

    fig.update_layout(
        updatemenus=[dict(
            type='buttons',
            showactive=False,
            buttons=[dict(label='再生',
                          method='animate',
                          args=[None, {'frame': {'duration': 500, 'redraw': True}, 'fromcurrent': True}]),
                     dict(label='一時停止',
                          method='animate',
                          args=[[None], {'frame': {'duration': 0, 'redraw': True}, 'mode': 'immediate', 'transition': {'duration': 0}}])]
        )]
    )

    return fig

app.layout.children.append(dcc.Graph(id='animation-graph'))

このコードでは、Plotly Graph Objectsを使用して、月ごとの平均気温の変化を示すアニメーショングラフを作成しています。ユーザーは再生ボタンをクリックして、時間の経過とともに気温がどのように変化するかを観察できます。

第11章:地図の統合

地理的なデータを視覚化するために、ダッシュボードに地図を統合することができます。以下は、日本の主要都市の位置と気温データを地図上に表示する例です。

import plotly.graph_objects as go

@app.callback(
    Output('map-graph', 'figure'),
    Input('date-range', 'start_date'),
    Input('date-range', 'end_date')
)
def update_map(start_date, end_date):
    filtered_df = df[(df['date'] >= start_date) & (df['date'] <= end_date)]
    avg_temp = filtered_df.groupby('city').agg({'temperature': 'mean', 'latitude': 'first', 'longitude': 'first'}).reset_index()

    fig = go.Figure(data=go.Scattermapbox(
        lat=avg_temp['latitude'],
        lon=avg_temp['longitude'],
        mode='markers',
        marker=go.scattermapbox.Marker(
            size=avg_temp['temperature'],
            color=avg_temp['temperature'],
            colorscale='Viridis',
            showscale=True,
            colorbar=dict(title='平均気温 (°C)')
        ),
        text=avg_temp['city'] + ': ' + avg_temp['temperature'].round(1).astype(str) + '°C',
        hoverinfo='text'
    ))

    fig.update_layout(
        mapbox_style="open-street-map",
        mapbox=dict(
            center=dict(lat=36.2048, lon=138.2529),
            zoom=4
        ),
        title='日本の主要都市の平均気温',
        height=600
    )

    return fig

app.layout.children.append(dcc.Graph(id='map-graph'))

このコードでは、PlotlyのScattermapboxを使用して、OpenStreetMap上に各都市の位置をプロットしています。マーカーのサイズと色は平均気温を表しており、ユーザーが選択した日付範囲に基づいて動的に更新されます。

第12章:パフォーマンスの最適化

大規模なデータセットを扱う場合、ダッシュボードのパフォーマンスを最適化することが重要です。以下は、データのキャッシュと計算の最適化を行う例です。

from flask_caching import Cache

cache = Cache(app.server, config={
    'CACHE_TYPE': 'simple'
})

@cache.memoize(timeout=3600)
def get_data(city, start_date, end_date):
    filtered_df = city_data[city]
    return filtered_df[(filtered_df['date'] >= start_date) & (filtered_df['date'] <= end_date)]

@app.callback(
    Output('temperature-graph', 'figure'),
    Output('precipitation-graph', 'figure'),
    Output('data-table', 'data'),
    Input('city-dropdown', 'value'),
    Input('date-range', 'start_date'),
    Input('date-range', 'end_date')
)
def update_dashboard(selected_city, start_date, end_date):
    filtered_df = get_data(selected_city, start_date, end_date)
    
    temp_fig = px.line(filtered_df, x='date', y='temperature',
                       title=f'{selected_city}の気温推移',
                       labels={'temperature': '気温 (°C)', 'date': '日付'})
    
    precip_fig = px.bar(filtered_df, x='date', y='precipitation',
                        title=f'{selected_city}の降水量',
                        labels={'precipitation': '降水量 (mm)', 'date': '日付'})
    
    return temp_fig, precip_fig, filtered_df.to_dict('records')

このコードでは、Flask-Cachingを使用してデータのキャッシュを実装しています。get_data関数の結果は1時間キャッシュされ、同じパラメータで再度呼び出された場合に高速に結果を返します。これにより、特に大規模なデータセットを扱う場合にパフォーマンスが向上します。

第13章:エラー処理とユーザーフィードバック

ロバストなアプリケーションを作成するために、エラー処理とユーザーフィードバックを実装することが重要です。以下は、データ読み込み中の状態表示とエラーメッセージの表示を追加する例です。

app.layout = html.Div([
    html.H1('日本の気象データダッシュボード'),
    dcc.Loading(
        id="loading",
        type="circle",
        children=[
            html.Div([
                dcc.Graph(id='temperature-graph'),
                dcc.Graph(id='precipitation-graph'),
                dash_table.DataTable(id='data-table')
            ])
        ]
    ),
    html.Div(id='error-message', style={'color': 'red'})
])

@app.callback(
    Output('temperature-graph', 'figure'),
    Output('precipitation-graph', 'figure'),
    Output('data-table', 'data'),
    Output('error-message', 'children'),
    Input('city-dropdown', 'value'),
    Input('date-range', 'start_date'),
    Input('date-range', 'end_date')
)
def update_dashboard(selected_city, start_date, end_date):
    try:
        filtered_df = get_data(selected_city, start_date, end_date)
        
        temp_fig = px.line(filtered_df, x='date', y='temperature',
                           title=f'{selected_city}の気温推移',
                           labels={'temperature': '気温 (°C)', 'date': '日付'})
        
        precip_fig = px.bar(filtered_df, x='date', y='precipitation',
                            title=f'{selected_city}の降水量',
                            labels={'precipitation': '降水量 (mm)', 'date': '日付'})
        
        return temp_fig, precip_fig, filtered_df.to_dict('records'), ''
    except Exception as e:
        return {}, {}, [], f'エラーが発生しました: {str(e)}'

このコードでは、dcc.Loadingコンポーネントを使用してデータ読み込み中の状態を表示し、エラーが発生した場合にユーザーにフィードバックを提供します。これにより、ユーザーエクスペリエンスが向上し、問題が発生した場合にも適切に対応できます。

第14章:ダッシュボードのデプロイ

開発したダッシュボードを他のユーザーと共有するために、Herokuなどのクラウドプラットフォームにデプロイすることができます。以下は、Herokuにデプロイするための基本的な手順です。

  1. Herokuアカウントを作成し、Heroku CLIをインストールします。

  2. プロジェクトディレクトリにProcfileという名前のファイルを作成し、以下の内容を記述します:

web: gunicorn app:server
  1. requirements.txtファイルを作成し、必要なパッケージを列挙します:
dash==2.6.0
pandas==1.3.5
plotly==5.5.0
gunicorn==20.1.0
  1. Gitリポジトリを初期化し、変更をコミットします:
git init
git add .
git commit -m "Initial commit"
  1. Herokuアプリを作成し、デプロイします:
heroku create my-dash-app
git push heroku master
  1. アプリを開きます:
heroku open

これらの手順により、開発したダッシュボードがインターネット上で公開され、URLを知っている誰でもアクセスできるようになります。

第15章:セキュリティとユーザー認証

公開されたダッシュボードにセキュリティレイヤーを追加するために、基本的なユーザー認証を実装することができます。以下は、Flask-BasicAuthを使用して簡単な認証を追加する例です。

from flask_basicauth import BasicAuth

app = dash.Dash(__name__)
app.server.config['BASIC_AUTH_USERNAME'] = 'admin'
app.server.config['BASIC_AUTH_PASSWORD'] = 'password'
basic_auth = BasicAuth(app.server)

@app.server.before_request
@basic_auth.required
def before_request():
    pass

app.layout = html.Div([
    html.H1('セキュアな気象データダッシュボード'),
    # ... 他のコンポーネント ...
])

if __name__ == '__main__':
    app.run_server(debug=True)

このコードでは、Flask-BasicAuthを使用して、ダッシュボードへのアクセスを制限しています。ユーザーは、指定されたユーザー名とパスワードを入力する必要があります。これにより、許可されたユーザーのみがダッシュボードにアクセスできるようになります。

from flask_basicauth import BasicAuth

app = dash.Dash(__name__)
app.server.config['BASIC_AUTH_USERNAME'] = 'admin'
app.server.config['BASIC_AUTH_PASSWORD'] = 'password'
basic_auth = BasicAuth(app.server)

@app.server.before_request
@basic_auth.required
def before_request():
    pass

app.layout = html.Div([
    html.H1('セキュアな気象データダッシュボード'),
    dcc.Dropdown(
        id='city-dropdown',
        options=[{'label': city, 'value': city} for city in city_data.keys()],
        value='tokyo'
    ),
    dcc.DatePickerRange(
        id='date-range',
        start_date=df['date'].min(),
        end_date=df['date'].max(),
        display_format='YYYY-MM-DD'
    ),
    # グラフやテーブルの追加
])

if __name__ == '__main__':
    app.run_server(debug=True)

セキュリティのベストプラクティス

  1. HTTPSの使用: データの送受信時にセキュリティを確保するために、HTTPSを使用することが重要です。Herokuなどのホスティングサービスでは、SSL証明書が自動的に適用されることが一般的です。

  2. 環境変数の利用: ユーザー名やパスワードなどの機密情報は、コード内にハードコーディングせず、環境変数として管理することを推奨します。これにより、コードの安全性が向上します。

  3. 認証システムの強化: より高度な認証システム(OAuthやJWTなど)を導入することで、セキュリティをさらに強化できます。

  4. 定期的な更新: 使用しているライブラリやフレームワークは定期的に更新し、最新のセキュリティパッチを適用することが重要です。

まとめ

ダッシュ(Dash)ライブラリは、Pythonを使ったインタラクティブなデータ可視化アプリケーションの開発を容易にし、多くの機能を提供します。このガイドでは、ダッシュの基本的な使い方から始まり、高度な機能やセキュリティ対策まで幅広くカバーしました。

最後に

ダッシュボードを作成することは、データサイエンスやビジュアル分析のスキルを向上させる素晴らしい方法です。ぜひ、自分自身のプロジェクトでダッシュを活用し、新しいデータ可視化体験を楽しんでください。今後も新しい機能や改善が期待されるダッシュライブラリで、自分だけのインタラクティブなアプリケーションを作成してみましょう!

このガイドがあなたのダッシュボード作成に役立つことを願っています。質問やフィードバックがあれば、ぜひお知らせください。

0
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?