よしたく blog

ほぼ週刊で記事を書いています

pipenvでpipenv install throws --system is intended to be used for pre-existing Pipfile installationが発生したときの解決方法

pipenvを触り始めて試行錯誤していたら環境を壊してしまったようで、同名のフォルダを再作成してもうまくいかず、pipenv install throws --system is intended to be used for pre-existing Pipfile installationが発生した。

まずはpipenv --venvで仮想環境の一覧を確認し、壊してしまった仮想環境のパスを確認する。

その後、rm -rf {{パス}}を実行すれば、同名のフォルダで再作成することができた。

Pythonの%timeで出力される値の見方

Pythonの%timeで出力される値の見方を調べたので、メモしておく。

yoshitaku-jp.hatenablog.com

CPU times: user 14 µs, sys: 0 ns, total: 14 µs
Wall time: 19.1 µs

と表示されているとき、 Wall time: 19.1 µsは全体の実行時間を表している。 CPU times: user 14 µsはユーザスペースでのCPU実行時間を表している。 sys: 0 nsカーネルスペースでのCPU実行時間を表している。 total: 14 µsはユーザスペースとカーネルスペースでのCPU実行時間の合計を表している。

Jupyter Notebookでコードの速度を計測するtimeとtimeit

Jupyte Notebook でコードの速度を計測する方法にマジックコマンドとして用意されている、timetimeitを使う方法がある。 この 2 つの存在は知っていたが、

  • time と timeit で何が違うのか
  • %の数が 1 つのときと 2 つのときで何が違うのか

がわからなかったので、確認してみた!

%time と %%time

%time

%time は 1 行毎の処理時間を 1 回計測する。 例で上げたコードは、ループの中で%timeを使っているので、出力結果には 10 回分の結果が表示されている。

dist = []
for i in range(10):
    %time dist.append(i ** 2)
CPU times: user 7 µs, sys: 1e+03 ns, total: 8 µs
Wall time: 13.4 µs
CPU times: user 7 µs, sys: 0 ns, total: 7 µs
Wall time: 10.5 µs
CPU times: user 5 µs, sys: 0 ns, total: 5 µs
Wall time: 8.58 µs
CPU times: user 6 µs, sys: 1 µs, total: 7 µs
Wall time: 11.2 µs
CPU times: user 6 µs, sys: 0 ns, total: 6 µs
Wall time: 9.78 µs
CPU times: user 5 µs, sys: 0 ns, total: 5 µs
Wall time: 9.3 µs
CPU times: user 5 µs, sys: 0 ns, total: 5 µs
Wall time: 9.3 µs
CPU times: user 4 µs, sys: 0 ns, total: 4 µs
Wall time: 6.68 µs
CPU times: user 3 µs, sys: 0 ns, total: 3 µs
Wall time: 5.72 µs
CPU times: user 4 µs, sys: 0 ns, total: 4 µs
Wall time: 5.72 µs

結果の見方についてはこちら

yoshitaku-jp.hatenablog.com

%%time

次に%が 2 つになった%%timeの動きを確認する。%%time は セルの処理時間を 1 回計測する。

%%time
dist = []
for i in range(10):
    dist.append(i ** 2)
CPU times: user 14 µs, sys: 0 ns, total: 14 µs
Wall time: 19.1 µs

%timeit と %%timeit

%timeit

%timeit は 1 行毎の処理時間を 複数回計測し平均処理時間を表示する。

%timeit [x ** 2 for x in range(10000)]
%timeit [x ** 2 for x in range(10000)]
%timeit [x ** 2 for x in range(10000)]
100 loops, best of 5: 2.77 ms per loop
100 loops, best of 5: 2.74 ms per loop
100 loops, best of 5: 2.74 ms per loop

%%timeit

次に%が 2 つになった%%timeitの動きを確認する。%%timeit は セルの処理時間を 複数回計測し平均処理時間を表示する。

%%timeit
[x ** 2 for x in range(10000)]
[x ** 2 for x in range(10000)]
[x ** 2 for x in range(10000)]
100 loops, best of 5: 8.39 ms per loop

実行回数の変更方法

timeitはデフォルトで 100 回ループしてくれる。 この回数を変更するためには-n オプションで回数を指定する。

%%timeit -n 1000
[x ** 2 for x in range(10000)]
[x ** 2 for x in range(10000)]
[x ** 2 for x in range(10000)]
1000 loops, best of 5: 8.34 ms per loop

さらにrで繰り返す回数も変更できる。

%%timeit -n 100 -r 10
[x ** 2 for x in range(10000)]
[x ** 2 for x in range(10000)]
[x ** 2 for x in range(10000)]
100 loops, best of 10: 8.25 ms per loop

まとめ

まとめると次のようになる。

Pandasで欠損値を埋めるための方法

Pandasで欠損値を埋めるための方法をいくつか確認したのでまとめておく。fillna関数を使うと引数に渡した値で、NaNを埋められる。

まずは適当にNaNを含んだデータを生成する。

import numpy as np
import pandas as pd

df = pd.DataFrame(data=[1,2,4,np.nan,5,8,7,np.nan], columns=['col_nan'])
df
index col_nan
0 1.0
1 2.0
2 4.0
3 NaN
4 5.0
5 8.0
6 7.0
7 NaN

ゼロで埋める

fillna関数を使うと引数に渡したもので、NaNを埋められる。 fillna(0)とするとNaNをゼロ埋め、fillna(5)とするとNaNを5で埋めてくれる。

df['col_fill_0'] = df.col_nan.fillna(0)
df
index col_nan col_fill_0 col_fill_mean
0 1.0 1.0 1.0
1 2.0 2.0 2.0
2 4.0 4.0 4.0
3 NaN 0.0 4.5
4 5.0 5.0 5.0
5 8.0 8.0 8.0
6 7.0 7.0 7.0
7 NaN 0.0 4.5

平均値で埋める

mean()関数を使って列の平均値を求め、その平均値を使ってNaNを埋めることができる。

df['col_fill_mean'] = df.col_nan.fillna(df.col_nan.mean())
df
index col_nan col_fill_0 col_fill_mean
0 1.0 1.0 1.0
1 2.0 2.0 2.0
2 4.0 4.0 4.0
3 NaN 0.0 4.5
4 5.0 5.0 5.0
5 8.0 8.0 8.0
6 7.0 7.0 7.0
7 NaN 0.0 4.5

前後の値を元に補間で埋める

補間という言葉を知らなかったが、調べたら次のように出てきた。

補間(ほかん)とは、ある既知の数値データ列を基にして、そのデータ列の各区間の範囲内を埋める数値を求めること、またはそのような関数を与えること。

interpolate関数を使うと、前後の値を元に補間してくれる。

df['col_fill_interpolate'] = df.col_nan.fillna(df.col_nan.interpolate())
df
index col_nan col_fill_0 col_fill_mean col_fill_interpolate
0 1.0 1.0 1.0 1.0
1 2.0 2.0 2.0 2.0
2 4.0 4.0 4.0 4.0
3 NaN 0.0 4.5 4.5
4 5.0 5.0 5.0 5.0
5 8.0 8.0 8.0 8.0
6 7.0 7.0 7.0 7.0
7 NaN 0.0 4.5 7.0

まとめ

今後使えそうな、NaNを埋める方法を3つ確認しておいた。なかなかゼロ埋めを多用することはなさそうなので、平均値か補間を使うことになりそう。ただ、補間は並び方でも偏りが出そうなので気をつけたい。

PythonのCounterクラスを使って要素の出現回数を集計する

Pythonでは、よく使われる組み込みコンテナの dictlistsettuple に代わる特殊なコンテナがあり、collectionsモジュールにまとまっている。今回はその中にある辞書型のサブクラスであるCounterを使ってリストなどの要素の出現回数を数え、簡単に集計をしてみる。

まずは、Counterを使わなかった場合の実装になる。 printと空行を除けば、7行での実装になる。

programming_languages = ['Python', 'Ruby', 'Go','Python','JavaScript', 'Go','Python','TypeScript','Python','TypeScript']

count = {}
for pl in programming_languages:
  if pl in count:
    count[pl] += 1
  else:
    count[pl] = 1    
print(count)

Counterを使うと下記のようになる。 printと空行を除けば、3行での実装になり、シンプルになった。

from collections import Counter

programming_languages = ['Python', 'Ruby', 'Go','Python','JavaScript', 'Go','Python','TypeScript','Python','TypeScript']

count = Counter(programming_languages)
for k, v in count.items():
    print(k + ':' + str(v))

返ってくるのが辞書型なので、items()などのメソッドを使うこともできる!

7月よりブログ更新を再開します

7月より週1でのブログ更新を再開します。と言う宣言です。 集中して取り組みたいことがあり、こっそり5,6月と休んでおりました。 その中で改めてブログに助けられたことがあり、休みつつも「落ち着いたら再開しよう!」という気持ちが高まっておりました。 というわけで、引き続き自分のペースでがんばります🙏

これを見た人も、まずは残り半年一緒に頑張りましょう💪

MinIOをboto3を使ってPythonから操作する

Python で S3 を操作するときに使う、AWS SDK for Python(boto3) を使って MinIO も操作できる。 以前作った環境を利用し、Python から操作できるか確認する。

yoshitaku-jp.hatenablog.com

準備

インストール

pip で boto3 をインストールしておく。

pip install boto3

コピー元ファイルの配置

ファイルアップロード機能を確認する際、minio から minio へファイルをコピーするような形で実現する。 今回はsystem-drummondバケットを作成し、test.mdファイルを作成して配置しておいた。

接続情報の確認

接続は client を使い、接続のための情報を設定する。

  • endpoint_url
    • 起動している Docker を指定
  • aws_access_key_id
    • ユーザ ID
    • 前回記事の Docker よりminioを設定
  • aws_secret_access_key
    • パスワード
    • 前回記事の Docker よりminiominiominioを設定
s3 = boto3.client(
    "s3",
    endpoint_url="http://127.0.0.1:9000",
    aws_access_key_id="minio",
    aws_secret_access_key="miniominiominio",
)

バケットの作成

import boto3

s3 = boto3.client(
    "s3",
    endpoint_url="http://127.0.0.1:9000",
    aws_access_key_id="minio",
    aws_secret_access_key="miniominiominio",
)
response = s3.list_buckets()
print(response["Buckets"])

s3.create_bucket(Bucket="from-boto")

response = s3.list_buckets()
print(response["Buckets"])
[{'Name': 'system-drummond', 'CreationDate': datetime.datetime(2022, 4, 29, 2, 4, 53, 547000, tzinfo=tzutc())}]
[{'Name': 'from-boto', 'CreationDate': datetime.datetime(2022, 4, 29, 2, 49, 46, 32000, tzinfo=tzutc())}, {'Name': 'system-drummond', 'CreationDate': datetime.datetime(2022, 4, 29, 2, 4, 53, 547000, tzinfo=tzutc())}]

まず初期状態をs3.list_buckets()で確認すると、準備で作成したsystem-drummondが存在していることがわかる。 s3.create_bucket(Bucket="from-boto")from-botoバケットを作成し、再度s3.list_buckets()で確認するとfrom-botoバケットが作成されていることが確認できる。

ファイルアップロード

import boto3

s3 = boto3.client(
    "s3",
    endpoint_url="http://127.0.0.1:9000",
    aws_access_key_id="minio",
    aws_secret_access_key="miniominiominio",
)

object_list = s3.list_objects(Bucket="from-boto").get("Contents")
print(object_list)

s3.upload_file("./minio/data/system-drummond/test.md", "from-boto", "test.md")

object_list = s3.list_objects(Bucket="from-boto").get("Contents")
print(object_list)
None
[{'Key': 'test.md', 'LastModified': datetime.datetime(2022, 4, 29, 3, 4, 45, 30000, tzinfo=tzutc()), 'ETag': '"2debfdcf79f03e4a65a667d21ef9de14"', 'Size': 5, 'StorageClass': 'STANDARD', 'Owner': {'DisplayName': 'minio', 'ID': '02d6176db174dc93cb1b899f7c6078f08654445fe8cf1b6ce98d8855f66bdbf4'}}]

こちらも、まずは初期状態を確認する。 from-botos3.list_objects(Bucket="from-boto").get("Contents")で確認すると、Noneであることがわかる。 「コピー元ファイルの配置」で配置したtest.mdを、s3.upload_file("./minio/data/system-drummond/test.md", "from-boto", "test.md")でアップロード処理する。 再びs3.list_objects(Bucket="from-boto").get("Contents")で見ると、test.mdが追加されていることがわかる。

バケットの削除

import boto3

s3 = boto3.client(
    "s3",
    endpoint_url="http://127.0.0.1:9000",
    aws_access_key_id="minio",
    aws_secret_access_key="miniominiominio",
)
response = s3.list_buckets()
print(response["Buckets"])

s3.delete_object(Bucket="from-boto", Key="test.md")
s3.delete_bucket(Bucket="from-boto")

response = s3.list_buckets()
print(response["Buckets"])
[{'Name': 'from-boto', 'CreationDate': datetime.datetime(2022, 4, 29, 2, 49, 46, 32000, tzinfo=tzutc())}, {'Name': 'system-drummond', 'CreationDate': datetime.datetime(2022, 4, 29, 2, 4, 53, 547000, tzinfo=tzutc())}]
[{'Name': 'system-drummond', 'CreationDate': datetime.datetime(2022, 4, 29, 2, 4, 53, 547000, tzinfo=tzutc())}]

from-botosystem-drummondが取得できる。 s3.delete_object(Bucket="from-boto", Key="test.md")でオブジェクトの中身のtest.mdを削除、その直後にs3.delete_bucket(Bucket="from-boto")from-botoバケットを削除している。 無事に削除できているので、最終的に表示されるのはsystem-drummondバケットだけになっている。

まとめ

Python から MinIO を触り、オブジェクト操作をおこなってみた。 S3 同様にcreate_bucketupload_filedelete_objectといった標準的な部分を使えることが確認できた。

【Rails】HerokuでPumaを使用する

Heroku の dyno リソースを理解できておらず、rails serverでアプリケーションを起動していた。 Heroku は Web サーバとして Puma を推奨しているので、Puma で明示的に起動できるように設定を変更してみる。

puma.io

Procfile

設定をせずシンプルに起動させるならば、Procfile に下記を記述する。

web: bundle exec puma

通常、rails new をするとconfig/puma.rbにファイルが生成されている。 そして-Cconfig/puma.rbを指定すると、設定ファイルに書き込まれた設定を読み込んでくれる。

web: bundle exec puma -C config/puma.rb

config/puma.rb

config/puma.rbの中身も確認してみる。 Rails 7.0.2.3では次のようになっている。

max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
threads min_threads_count, max_threads_count

worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development"

port ENV.fetch("PORT") { 3000 }

environment ENV.fetch("RAILS_ENV") { "development" }

pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }

plugin :tmp_restart

max_threads_countで使用する最大のスレッド数を決める。環境変数RAILS_MAX_THREADSがなければ 5 が設定される。 min_threads_countは使用する最低のスレッド数を決める。環境変数RAILS_MIN_THREADSがなければ max_threads_count を設定することになり、今回の場合は 5 となる。 threadsで、max_threads_countmin_threads_countの値を利用し設定する。

worker_timeoutは、指定された時間内にすべてのワーカーがマスタープロセスにチェックインしたことを確認する。確認できない場合ワーカープロセスが再開される。 "RAILS_ENV"が設定されていれば利用し、なければdevelopmentとなり、3600 が設定される。

portは利用するポートになる。 environmentは実行環境を指定する。

pidfileは、環境変数PIDFILEを利用し、なければtmp/pids/server.pidから値を取得する。

pluginは Puma へ機能追加するときに使用する。今回の場合はサーバの再起動を可能にするプラグインが設定されている。

Rails7でTailwind CSSを試す

Rails7 になって TailwindCSS が使えるようになったので、導入方法をメモしておく。

rails new

rails new をするタイミングで tailwind を導入するには--css tailwindオプションを指定する。

rails new myapp --css tailwind
cd myapp

このあたりは定型文になるが載せておく。

rails g scaffold todo name:string
rails db:create db:migrate

rails sをしてhttp://127.0.0.1:3000/todosにアクセスする。

f:id:yoshitaku_jp:20220417112311p:plain

変わっていない…

Rails7 では起動方法に注意

Rails 7 ではrails sコマンドを実行しても JavaScriptCSS がビルドされず、./bin/devコマンドで起動すると JavaScriptCSS がビルドされる。 ブラウザをリフレッシュすると TailwindCSS が適用されていることがわかる。

f:id:yoshitaku_jp:20220417112446p:plain

また、./bin/devで起動すると JavaScriptCSS を変更したときに自動的に再ビルドもしてくれるので、今後はこちらで起動することを忘れないようにしたい。

Docker Compose環境のRailsをHerokuへデプロイする

Docker Compose 環境で作成した Rails アプリケーションを Heroku へアップロードするには Git を使用したデプロイとは別のコマンド実行が必要になる。 例えば Git を使用するときはgit push heroku mainなどでデプロイができたが、Docker Compose 環境は Heroku CLI ツールを使うことになる。 今回は Docker Compose 環境をデプロイするための手順をまとめる。

Dockerfile と docker-compose.yml

Dockerfile と docker-compose.yml も参考として載せておく。

Dockerfile

FROM ruby:3.1.0

ENV APP_ROOT /myapp
RUN mkdir $APP_ROOT
WORKDIR $APP_ROOT
ADD . $APP_ROOT
RUN bundle install

CMD ["rails", "server", "-b", "0.0.0.0"]

docker-compose.yml

version: '3'
services:
  db:
    image: postgres
    ports:
      - '5432:5432'
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: 'admin'
      POSTGRES_PASSWORD: 'admin-pass'
  web:
    build: .
    volumes:
      - .:/myapp
    command: /bin/sh -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'"
    ports:
      - 3000:3000
    depends_on:
      - db

Docker Compose 環境をデプロイするまで

Heroku の Container Registry にログインする。

heroku container:login

Heroku アプリを作成する。

heroku create

Container Registry に docker image を push する。

heroku container:push web

push した docker image を Heroku アプリにリリースする。

heroku container:release web

まとめ

Docker Compose 環境で作成した Rails アプリケーションを Heroku へアップロードする手順を確認した。 無事にアップロードできたが、毎回コマンドを打ち込まなければいけないのが大変とも感じる。 heroku.ymlを使ってデプロイする方法もあるので、こちらも確認していく。