Pythonでcsv形式からParquet形式データに変換

Parquet形式のデータは列ベースのデータフォーマットで、Apacheプロジェクトの一つです。商用サービスだとDatabricksが有名ですね。

Parquet形式の利点は行ベースのデータ形式(csvとか)と比べて、特定の列データだけを扱うクエリが非常に高速になることです。また、効率的なデータ圧縮により容量も小さく済みます。

まずは事始めとして、国税庁の法人データcsvで取得して、それをParquet形式に変換してみました。

 

コード:

csvをParquet形式データに変換するコードは下記です。


import pandas as pd
import pyarrow as pa
import pyarrow.parquet as pq

# ダウンロードしたcsvを読み込む 
df = pd.read_csv('./00_zenkoku_all_20230428.csv', encoding="utf-8")

table = pa.Table.from_pandas(df)

# Parquet形式データに変換する
pq.write_table(table, './00_zenkoku_all_20230428.parquet')

Parquet形式データをpandasのデータフレームに展開して中身を確認してみます

import pyarrow.parquet as pq
 
table = pq.read_table("./00_zenkoku_all_20230428.parquet")
df = table.to_pandas()
 
df.info()
結果:

以下のように成功しました。約530万件(5307284件)のデータとなりました。メモリは1.2GBも使っています。データのファイルサイズは、csv形式が1.04 GBだったのに対して、 Parquet形式データは275 MBとかなり小さくなりました。

カラム名がデータの一行目になっているのが変ですね。ちゃんと扱うには、別途xml形式のダウンロードデータもあるので、そこからカラム名を取り出して別定義するなどの手間が必要です。ということで、カラム名を定義したものがこちら。

コード:

import pandas as pd
import pyarrow as pa
import pyarrow.parquet as pq

# カラム名を定義
column_names = ['sequenceNumber', 'corporateNumber', 'process', 'correct', 'updateDate', 'changeDate', 'name', 'nameImageId', 'kind', 'prefectureName', 'cityName', 'streetNumber', 'addressImageId', 'prefectureCode', 'cityCode', 'postCode', 'addressOutside', 'addressOutsideImageId', 'closeDate', 'closeCause', 'successorCorporateNumber', 'changeCause', 'assignmentDate', 'latest', 'enName', 'enPrefectureName', 'enCityName', 'enAddressOutside', 'furigana', 'hihyoji']

# CSVデータを読み込む
df = pd.read_csv('./00_zenkoku_all_20230428.csv', encoding="utf-8", names=column_names)

table = pa.Table.from_pandas(df)

# Parquet形式データに変換する
pq.write_table(table, './00_zenkoku_all_20230428.parquet')
結果:

別途、xmlデータのタグ名から取り出したカラム名の候補と、csvの実データをスプレッドシート上で並べて、ズレがないかを目視確認したのち、カラム名をコード中にセットして、成功しました。以下のようにカラムがきちんとセットされて、奇麗なデータフレームになりました。

ChatGPTに、飲酒後のアルコール分解にかかる時間を計算するPythonコードを書いてもらった

ChatGPTに、飲酒後のアルコール分解にかかる時間を計算するPythonコードを書いてもらいました。体重と飲んだお酒の種類、量を標準入力して、計算結果を表示するという単純なものです。

 

指示文(プロンプト):

飲酒後にアルコールが分解されるまでの時間を概算するPythonアプリを作りたいです。
以下の仕様に沿ったコードを提示してください。

仕様:
・標準入力で、体重を指定する
・標準入力で、飲んだお酒と量を指定する
・アルコール量(g) = 飲んだお酒の量(ml) × アルコール度数(%) × 0.8 ÷ 100
・1時間で分解できるアルコール量を計算する。計算式は「 体重 (kg) × 0.1 = 1時間で分解できるアルコール量(g) 」
・選択肢として表示する飲んだ酒の種類、量、含まれるアルコール量は別に定義。定義内容は、酒の種類×単位(本、杯など)
・最後に摂取したアルコール量と分解にかかる時間(時間単位)で表示してください。

 

回答:

def calculate_time_to_breakdown_alcohol():
    weight = float(input("体重を入力してください(kg): "))
    
    # アルコール度数
    alcohol_type = {"ビール": 5, "日本酒": 15, "焼酎": 25, "ワイン": 12} 

    # アルコール摂取単位(ml)
    alcohol_amount = {"ビール": 500, "日本酒": 180, "焼酎": 180, "ワイン": 100} 
    
    alcohol_list = []

    while True:
        alcohol = input("飲んだお酒を選択してください(ビール、日本酒、焼酎、ワイン)。終了する場合は「q」を入力してください: ")
        if alcohol == "q":
            break
        amount = float(input(f"{alcohol}の飲んだ量を入力してください(単位:ビールなら中瓶の本数、日本酒なら合、焼酎・ワインなら杯): "))
        alcohol_list.append((alcohol, amount))

    # 飲んだアルコール量(g)
    alcohol_content = sum([alcohol_amount[alcohol] * alcohol_type[alcohol] * amount for alcohol, amount in alcohol_list]) * 0.8 / 100 

    # 1時間で分解できるアルコール量(g)
    breakdown_alcohol_content = weight * 0.1

    # アルコールが分解されるまでの時間(時)
    breakdown_time = (alcohol_content / (breakdown_alcohol_content))
    
    # 結果出力
    print(f"飲んだアルコール量は{alcohol_content:.2f}gです。")
    print(f"アルコールが分解されるまでの時間は{breakdown_time:.2f}時間です。")

    
#実行
calculate_time_to_breakdown_alcohol()
    
結果:

以下のように成功しました。アルコールの度数、単位がコードに直接定義されているので、ここは外部化したほうが良さそうですね。

ただ、最初に提示されたコードの計算式が間違っていて、アルコール摂取単位をmlで定義しているのに飲んだ量もmlで入力させるようになっていたので、ビール1本でアルコール量が7000gとかになってしまったため、プロンプトで訂正も面倒だったので手動で訂正してます。

 

テキストファイルから、タグの名前だけを取り出すPythonコード

htmlやxmlなどのテキストファイルから、タグの名前だけを取り出すPythonコードです。例えば、<tagname>~</tagname>のtagnameだけを取り出したいときに使用します。

取り出したい情報は正規表現で探しているので、正規表現を変えれば、ほかの用途でも使えます。

コード:

import re

def extract_matching_parts(file_path, regex_pattern):
    with open(file_path, 'r') as file:
        text = file.read()
        matches = re.findall(regex_pattern, text)
        for match in matches:
            print(match)

# テキストファイルのパスと正規表現パターンを指定します
file_path = 'path/to/your/file.txt'
regex_pattern = r'<([^>]+)>'

# 関数を呼び出してマッチする部分を抽出し、出力します
extract_matching_parts(file_path, regex_pattern)

ChatGPT_APIで任意のテキストを短くしたり長くするPythonコード

ChatGPTのAPIを利用して、任意のテキストを短くしたり長くするPythonコードです。

APIに投げるprompt部分をもう少し詳細に指定するようにすれば、どのくらいの短さ(長さ)にするかも制御できるかもしれません。


import openai

def convert_to_short(prompt):
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": "You are a helpful assistant."},
            {"role": "user", "content": "次の文章を短くしてください。"},
            {"role": "user", "content": prompt}
        ],
        max_tokens=500
    )

    reply = response.choices[0].message.get('content', '')
    return reply

def convert_to_long(prompt):
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": "You are a helpful assistant."},
            {"role": "user", "content": "次の文章を長くしてください。"},
            {"role": "user", "content": prompt}
        ],
        max_tokens=500
    )

    reply = response.choices[0].message.get('content', '')
    return reply

# OpenAI APIの設定
openai.api_key = "API Keyをここに入れる"

input_sentence = input('文章を入力してください: ')
mode = input('どちらに変換するかを選択してください(短く: 1, 長く: 2): ')

if mode == '1':
    converted_sentence = convert_to_short(input_sentence)
elif mode == '2':
    converted_sentence = convert_to_long(input_sentence)
else:
    print('無効なモードが選択されました。')

print('変換結果:', converted_sentence)

    
結果:

以下のように成功しました。サンプルとして、適当なニュース記事を入れてます。

 

 

ChatGPT_APIで任意のテキストを敬体・常体変換させるためのPythonコード

ChatGPTに敬体・常体を変換するPythonコードを書いてもらいました。

最初に提示されたのはシンプルな置換ルールを定義するもので、実用には堪えません。

敬体・常体変換・統一を高い精度で行う良いライブラリも見当たらず、現時点では自力で機械学習などで厳密なルールベースを構築する必要がありそうです。

そこで、敬体・常態変換タスクをChatGPTなりBing_AI-Chatにさせたほうが手っ取り早いので、ChatGPT(OpenAI)にAPIで変換指示を投げ込むコードも書いてもらいました。

以下、単純置換ベースとOpenAI-APIベースの二つを比較してます。

 

指示文1(プロンプト) 単純な置換ベースの場合:

入力された文章に対して、敬体もしくは常体に適宜変換して出力するPythonコードを提示してください。

 

回答:

以下は、Pythonで敬体もしくは常体に文章を変換するためのサンプルコードです。敬体と常体の変換は非常に複雑な問題であり、完璧な変換を行うことは難しいですが、基本的なパターンに対して簡単な変換を行うことができます。


def convert_to_honorific(sentence):
    honorific_mapping = {
        'です': 'ます',
        'である': 'でございます',
        'だ': 'です',
        'ですか': 'ますか',
        'でしょうか': 'でしょうか',
        'でしょう': 'でしょう',
        'だろうか': 'でしょうか',
        'だろう': 'でしょう',
        'ない': 'ありません',
        'ません': 'ません',
    }

    for key, value in honorific_mapping.items():
        sentence = sentence.replace(key, value)

    return sentence


def convert_to_plain(sentence):
    plain_mapping = {
        'ます': 'です',
        'でございます': 'である',
        'ですか': 'だ',
        'でしょうか': 'だろうか',
        'でしょう': 'だろう',
        'ありません': 'ない',
        'ません': 'ない',
    }

    for key, value in plain_mapping.items():
        sentence = sentence.replace(key, value)

    return sentence


input_sentence = input('文章を入力してください: ')
mode = input('変換モードを選択してください(敬体: 1, 常体: 2): ')

if mode == '1':
    converted_sentence = convert_to_honorific(input_sentence)
elif mode == '2':
    converted_sentence = convert_to_plain(input_sentence)
else:
    print('無効なモードが選択されました。')

print('変換結果:', converted_sentence)
    

このコードでは、convert_to_honorific関数とconvert_to_plain関数を定義しています。convert_to_honorific関数は常体の文章を敬体に変換し、convert_to_plain関数は敬体の文章を常体に変換します。

プログラムは、ユーザーに文章と変換モード(敬体または常体)を入力してもらいます。入力されたモードに応じて適切な変換関数を呼び出し、変換結果を表示します。

ただし、このコードは単純な置換に基づいており、全ての文脈に対して適切な変換を行うわけではありません。実際の敬体や常体の変換は非常に複雑であり、文化や状況によっても異なる場合があります。したがって、このコードはあくまで簡易的な例であり、完璧な変換を行うためにはより高度な自然言語処理の手法が必要です。

結果:

一応、以下のように成功はしましたが、シンプルな文章にしか使えません。上記コード中のmappingをもっと充実させれば、それなりには・・・とも思いましたが、少なくとも同じ文字列(表現)でも文末と文中を判別するなどの処理がないと厳しそうですね。

 

指示文(プロンプト)2_OpenAIのAPIを利用する場合:

ChatGPTに、APIを通して、敬体・常体を変換するタスクを行わせるPythonコードを書いてください。

回答:

以下は、OpenAIのChatGPT APIを使用して、敬体・常体の変換タスクを実行するPythonコードの例です。このコードは、OpenAIのopenai.ChatCompletion.create()メソッドを呼び出してAPIリクエストを送信し、応答を取得します。


import openai

def convert_to_honorific(prompt):
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": "You are a helpful assistant."},
            {"role": "user", "content": "次の文章を敬体に変換してください。"},
            {"role": "user", "content": prompt}
        ],
        max_tokens=50,
        temperature=0.6
    )

    reply = response.choices[0].message.get('content', '')
    return reply

def convert_to_plain(prompt):
    response = openai.ChatCompletion.create(
        model="gpt-3.5-turbo",
        messages=[
            {"role": "system", "content": "You are a helpful assistant."},
            {"role": "user", "content": "次の文章を常体に変換してください。"},
            {"role": "user", "content": prompt}
        ],
        max_tokens=50,
        temperature=0.6
    )

    reply = response.choices[0].message.get('content', '')
    return reply

# OpenAI APIの設定
openai.api_key = "YOUR_API_KEY"

input_sentence = input('文章を入力してください: ')
mode = input('変換モードを選択してください(敬体: 1, 常体: 2): ')

if mode == '1':
    converted_sentence = convert_to_honorific(input_sentence)
elif mode == '2':
    converted_sentence = convert_to_plain(input_sentence)
else:
    print('無効なモードが選択されました。')

print('変換結果:', converted_sentence)
    
    

上記のコードでは、OpenAI APIのクライアントライブラリを使用して、openai.ChatCompletion.create()メソッドを呼び出してAPIリクエストを送信しています。APIキーを適切に設定する必要があります。

convert_to_honorific関数とconvert_to_plain関数は、敬体と常体の変換を実行するためにAPIに対してメッセージを送信し、応答を取得します。APIリクエストのパラメータとして、モデルの指定、メッセージの役割と内容、最大トークン数、温度などを設定します。

ユーザーからの入力を受け取り、選択された変換モードに応じて適切な関数を呼び出し、変換結果を表示します。

注意点として、OpenAIのAPIは有料であり、適切なプランと課金情報が必要です。また、敬体・常体の変換は非常に複雑な問題であり、 完璧な変換を保証するものではありません。

結果:

以下のように成功しました。提示されたコードでは、「max_tokens=50」となってますが、これだと短すぎるので、実際には500にしてテストしてます。

単純置換ベースに比べて、圧倒的に高精度になりました。ただ、少し複雑な文章で試すと、文体自体から変えられ意味合いが少し変わってしまう場合も(汗)。実用的なツールにするには、もう少し絞った指示文を生成してから投げ込む必要がありそうです。



 

ChatGPTにライプニッツ級数で円周率を計算し、numbaの有無を比較するPythonコードを書いてもらった

ChatGPTに、numbaの有り無しでの処理速度を比較するため、ライプニッツ級数で円周率を計算するPythonコードを書いてもらいました。

\sum_{n=0}^{\infty}\frac{(-1)^n}{2n+1} = \frac{\pi}{4}

指示文(プロンプト):

ライプニッツ級数を用いて、円周率を計算するPythonコードを提示してください。和の上限を10^9としてください。numbaを利用する場合と利用しない場合、それぞれ提示してください。

numbaを利用する場合:


import numba

@numba.jit
def pi_leibniz(n):
    sum = 0
    for i in range(n):
        sum += (-1)**i / (2*i + 1)
    return sum * 4

print(pi_leibniz(10**9))

    

numbaを利用しない場合:


def pi_leibniz(n):
    sum = 0
    for i in range(n):
        sum += (-1)**i / (2*i + 1)
    return sum * 4

print(pi_leibniz(10**9))
    
結果:

計算結果は「3.1415926525880504」でした。あってますね。

以下が計測結果です。コードの違いは「@numba.jit」をつけるか否かですね。計測にはgnu版のtimeを使ってます。

numba無し:
real:281.06[sec]
user:281.00[sec]
sys:0.44[sec]
Memory:75996[KB]

numbaあり:
real:14.19[sec]
user:14.09[sec]
sys:0.50[sec]
Memory:121036[KB]

ChatGPTにNumpyを使うのと使わないので速度比較するPythonコードを書いてもらった

ChatGPTにNumpyを使う場合と使わない場合の計算速度を比較するPythonコードを書いてもらいました。

 

指示文(プロンプト):

numpy を使った計算と、使わない場合の計算速度を比較するPythonコードを教えてください。

 

回答1:

numpy を使った計算と、使わない場合の計算速度を比較するPythonコードは、以下のようになります。


import numpy as np
import time

# numpy を使わない場合
def calc_without_numpy(n):
    x = [[i for i in range(n)] for j in range(n)]
    y = [[0 for i in range(n)] for j in range(n)]
    start = time.time()
    for i in range(n):
        for j in range(n):
            y[i][j] = x[i][j] + x[(i+1)%n][j] - x[(i+1)%n][(j+1)%n] * x[i][(j+1)%n]
    end = time.time()
    return end - start

# numpy を使う場合
def calc_with_numpy(n):
    x = np.arange(n*n).reshape((n,n))
    y = np.zeros((n,n))
    start = time.time()
    y = x + np.roll(x, -1, axis=0) - np.roll(x, -1, axis=1) * np.roll(x, -1, axis=(0,1))
    end = time.time()
    return end - start

# 計算速度を比較する
n = 10000 # 行列のサイズ
t1 = calc_without_numpy(n)
t2 = calc_with_numpy(n)
print(f"numpy を使わない場合: {t1:.3f}秒")
print(f"numpy を使う場合: {t2:.3f}秒")
print(f"numpy の方が {t1/t2:.2f}倍速い")
    
結果1:

以下のように成功しました。

回答2:

import numpy as np
import time

# numpy を使わない場合
def calc_pi_without_numpy(n):
    start = time.time()
    x = 0
    for i in range(1, n+1):
        x += 1 / i**2
    pi = (6 * x)**0.5
    end = time.time()
    return pi, end - start

# numpy を使う場合
def calc_pi_with_numpy(n):
    start = time.time()
    x = np.arange(1, n+1)
    y = np.sum(1 / x**2)
    pi = (6 * y)**0.5
    end = time.time()
    return pi, end - start

# 計算速度を比較する
n = 100000000 # 繰り返し回数
pi1, t1 = calc_pi_without_numpy(n)
pi2, t2 = calc_pi_with_numpy(n)
print(f"numpy を使わない場合: {pi1:.6f}, {t1:.3f}秒")
print(f"numpy を使う場合: {pi2:.6f}, {t2:.3f}秒")
print(f"numpy の方が {t1/t2:.2f}倍速い")
    
結果2:

以下のように成功しました。計算内容は円周率を求めるものです。

 \pi=\sqrt{6\sum_{k=1}^\infty \frac{1}{k^2}}