Djangoでcsvをアップロード、pandasで処理しダウンロードするまで


はじめに

pythonでのデータ処理、機械学習を勉強中の初心者です。

今回は作成した回帰モデルを用い、アップロードされたテストファイルを読み込み、Notebookで行うようなpandasでのデータ処理を行い、結果のcsvファイルをweb上でダウンロードするところまでの機能を持つアプリを作成しました。

ネット上にはDjangoでpandasを使う方法があまり見当たらず、いろいろと調べて試しながら完全に自己流で作ったので至らぬ点もあるかとは思いますがご容赦ください。

環境と前提

Windows10
django 3.1.7
python 3.9.2

プロジェクト作成

$ django-admin startproject myproject
$ cd myproject
$ python manage.py startapp app

ディレクトリ構造

myproject/
  app/
    __pycache__/
    migrations/
    static/
      files/ (new)
        train_x.csv
        train_y.csv
    __init__.py
    admin.py
    apps.py
    forms.py (new)
    functions.py (new)
    models.py
    tests.py
    views.py
  myproject/
    __pycache__/
    __init__.py
    asgi.py
    settings.py
    urls.py
    wsgi.py
static/
templates/
  index.html
db.sqlite3
manage.py

目標イメージ

 フォームの作成

forms.pyを作成します。

forms.py
from django import forms

class UploadForm(forms.Form):
    testfile = forms.FileField()

これをindex.htmlで使うだけでよく見るファイルを選択するフォームが作れます。とても便利ですね。

index.html
<div class="form-group">
  <form method="POST" class="form" enctype="multipart/form-data">  
    {% csrf_token %}  
    <div class="file-upload-wrapper">
      <p>テストファイルを選択<br \>
        {{ form.testfile }}
      </p>   
    </div> 

最後にmyproject/urls.pyの設定

urls.py
from django.contrib import admin
from django.urls import path
from app import views

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', views.index, name='index'), (追加)
]

お決まりのsettings.pyの細かい設定

settings.py
(省略)
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app', (追加)
]
(省略)
TEMPLATES = [
    {
        (省略)
        'DIRS': [BASE_DIR, 'templates'],
        (省略)
    },
]

これでおそらく$ python manage.py runserverでローカルホストにアクセスするとファイル選択のフォームができているはずです。

アップロードされたファイルの処理

今回はfunctions.pyにファイル処理の関数を書き、それをviews.pyで使っていきます。

views.py
from django.shortcuts import render
from django.http import HttpResponse
from django.template import loader
from app.forms import UploadForm
from app.functions import process_file,to_csv
import csv,io
import pandas as pd

# Create your views here.
def index(request):
    if request.method == 'POST':
        upload = UploadForm(request.POST, request.FILES)
        if upload.is_valid():
            data = pd.read_csv(io.StringIO(request.FILES['testfile'].read().decode('utf-8')), delimiter=',')
            df = process_file(data)

            response = to_csv(df)

            return response
    else:
        upload = UploadForm()
        return render(request, "index.html", {'form':upload})

def index(request):から三行くらいまではformからPOSTで受け取るときの定型のようなものらしいです。
data = pd.read_csv(io.StringIO(request.FILES['testfile'].read().decode('utf-8')), delimiter=',')ここが一番苦労しました。
単にpd.read_csvだけでは読み取れず、いろいろ試しまくった結果これでいけました。

pandasでデータを処理していくprocess_file()と、データフレームをcsvファイルにしてダウンロードさせるto_csv()関数をfunctions.pyで作り、views.pyで使用する。

データの処理、ダウンロード

functions.py
import pandas as pd
import numpy as np
import sklearn,csv,re,os
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestRegressor
from django.http import HttpResponse
from myproject.settings import BASE_DIR

def process_file(data):
    #train_x = pd.read_csv("app/static/files/train_x.csv")
    #train_y = pd.read_csv("app/static/files/train_y.csv")
    train_x = pd.read_csv(os.path.join(BASE_DIR, 'app/static/files/train_x_modified.csv'))
    train_y = pd.read_csv(os.path.join(BASE_DIR, 'app/static/files/train_y_modified.csv'))
    test = data

    #お好きなようにデータ処理

    df_result = #最後にcsvに変換したいデータフレームとして返す

    return df_result

def to_csv(df):
    response = HttpResponse(content_type='text/csv; charset=UTF-8')
    response['Content-Disposition'] = 'attachment; filename="result.csv"'

    df.to_csv(path_or_buf = response, encoding = 'utf-8-sig', index=False)

    return response

データ処理に必要なモジュールをそれぞれインポートしておきます。

process_file()

まず、トレーニングデータをmyproject/app/static/files/から読み込んでいます。
ローカルではpd.read_csv("app/static/files/~")でも大丈夫だったのですが、サーバーにデプロイして環境が変わったときに読み込めなくなったのでBASE_DIR/app/static/files/~となるように設定しました。

(ちなみにBASE_DIRはsettings.pyで定義されており、デフォルトではmanage.pyの存在するディレクトリだそうです。つまりこの例ではmyproject/になります。)

その後は関数process_file()内でNotebookで行っているような処理を自由に書くことができて、最後にデータフレーム型で返してあげます。

to_csv()

process_file()のレスポンスとして帰ってきたデータフレームをcsvに変換し、ダウンロードします。

最初の二行は定型です。HttpResponseとして"result.csv"という名前のcsvファイルを返します。attachmentをつけることで、フォーム内のボタンをクリックすると自動的にダウンロードが開始されるようになります。

ここでも時間を使ったのがdata.to_csv(path_or_buf = response, encoding = 'utf-8-sig', index=False)の部分で、path_or_buf = responseとすることでデータフレームをcsvに変換しダウンロードまでつなげることができるようになりました。

本当の最後に、index.htmlのform内にボタンをつけてあげて完成です。

index.html
<div class="form-group">
  <form method="POST" class="form" enctype="multipart/form-data">  
    {% csrf_token %}  
    <div class="file-upload-wrapper">
      <p>テストファイルを選択<br \>
        {{ form.testfile }}
      </p>
    </div> 
    <p>テストファイルをアップロードし、予測結果の'result.csv'ファイルをダウンロードする</p>
    <button type="submit" class="save btn btn-outline-primary">Upload and Download</button>  
  </form> 
</div>

補足

今回は時間がなかったためアップロードされたファイルのバリデーションチェックは省略しました。

Djangoでpandasを使用する方法を調べていると、django-pandasというライブラリをインストールして使うという説明しか見当たらなかったのですが、python標準モジュールのpandasでもこのようにしてできるようです。