Python {Article008}

ようこそ「Python」へ...

Python Pandasにデータを取り込む(ロード)には【Pandas】

ここではPythonのライブラリの中でもっとも頻繁に利用されるPandasを徹底的に使いこなすためのシリーズ第1回目として、 PandasのDataFrameにデータをロード(作成)する方法を解説します。 PandasにはExcelファイル、CSVファイル、タブ区切りのテキストファイル、データベースなどからデータを取り込む機能があります。 外部ファイルからデータを取り込む方法については別記事にて解説します。 ここではテスト的に使用する数件のデータを作成する方法を中心に説明します。

この記事では、Pandasのライブラリを使用しますので 「記事(Article001) | 記事(Article002) | 記事(Article003) | 記事(Article004)」 を参照して事前にインストールしておいてください。 Pythonのコードを入力するときにMicrosoftのVisula Studio Codeを使用します。 まだ、インストールしていないときは「記事(Article001)」を参照してインストールしておいてください。

説明文の左側に図の画像が表示されていますが縮小されています。 画像を拡大するにはマウスを画像上に移動してクリックします。 画像が拡大表示されます。拡大された画像を閉じるには右上の[X]をクリックします。 画像の任意の場所をクリックして閉じることもできます。

PandasのDataFrameにデータをロードする

  1. 辞書型で列名と値を同時に定義する

    Visual Studio Code(VSC)を起動したら以下のコードを入力して実行します。 行2-4ではpandas, datetime, numpyのライブラリを取り込んでいます。 行7-12ではdict型(辞書型)の変数にPandasのDataFrameの列名と列の値(list型)を同時に定義しています。 つまり、dict型のkeyが列名でvalue(値)がlist型になっています。 行8では列名「category_name」と列の値を5レコード分str型で定義しています。 行9では列名「page_view」と列の値をint型で定義しています。 行10では列名「exclude_me」と列の値をbool型で定義しています。 行11では列名「date_added」と列の値をstr型で定義しています。 行14ではPandasのDataFrameに変数dataに格納されているデータを取り込んでいます。 行15では列名「date_added」のデータ型をstr型からdatetime型に変換しています。 行16ではDataFrameのinfo()メソッドを実行してDataFrameの各種情報を取得しています。 info()メソッドではDataFrameのレコード件数(5)、列数(4)、列名、列のデータ型などが取得できます。詳細は図1の実行結果をご覧ください。 行18ではDataFrameのすべてのデータを表示しています。
    # Import the necessary libraries
    import pandas as pd
    import datetime as dt
    import numpy as np
    
    # Create DataFrame from dict 
    data = {
        'category_name': ['JavaScript','jQuery','Python','C++','C#'],
        'page_view': [100,150,900,600,700],
        'exclude_me': [False, True, False, False, False],
        'date_added': ['2021-01-05 11:29:59','2021-02-25 12:13:15','2021-05-25 05:06:59','2020-11-09 03:05:02','2019-12-05 01:02:03']
    }
    #print(type(data))
    df = pd.DataFrame(data)
    df['date_added'] = pd.to_datetime(df['date_added'], format='%Y-%m-%d %H:%M:%S') # Convert string to datetime format
    print(df.info())
    print('~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~')
    print(df)
    click image to zoom!
    図1
    図1が実行結果です。print(df.info())とprint(df)で表示した内容が出力されています。
    click image to zoom!
    TIP1
    TIP1:Pythonのデータ型のCheet Sheetです。リスト型、辞書型などをどのように記述するかがわかります。

    TIP2:Pandasのto_datetime()の引数「errors='ignore'」の使い方
    ここでは行5の列「date_added」の値を意図的に不正な値「2021-01-05X 11:29:59」にしています。 Pandasのto_datetime()メソッドでstr型の日時をdatetime型に変換するとき、日時の形式が不正だとPandasはエラーとなり処理を中断します。 このとき不正な日時を検出してもエラーを無視して処理を続行させたいときは引数「errors='ignore'」を指定します。 ただし、列「date_added」のデータ型は「object型(str型)」になります。 一旦、不正なデータも含めてDataFrameに取り込んだらprint(df)でDataFrameの内容を表示して該当するデータを修正します。
    data = {
        'category_name': ['JavaScript','jQuery','Python','C++','C#'],
        'page_view': [100,150,900,600,700],
        'exclude_me': [False, True, False, False, False],
        'date_added': ['2021-01-05X 11:29:59','2021-02-25 12:13:15','2021-05-25 05:06:59','2020-11-09 03:05:02','2019-12-05 01:02:03']
    }
    df = pd.DataFrame(data)
    print(f'df.date_added.dtypes (before) = {df.date_added.dtypes}')
    df['date_added'] = pd.to_datetime(df['date_added'], format='%Y-%m-%d %H:%M:%S', errors='ignore') # Convert string to datetime format => Ignore
    print(f'df.date_added.dtypes (after)  = {df.date_added.dtypes}')
    print()
    print(df)
    click image to zoom!
    TIP2
    TIP2の実行結果です。列category_nameが「JavaScrip」の列「date_added」の値が不正な日時になっていますが無視されてDataFrameに格納されています。 この場合、print(df.date_added.dtypes)で確認するとデータ型が「object」になっているのがわかります。 あとは、print(df)でDataFrameの内容を表示し、目視で不正なデータを探して修正します。

    TIP3:Pandasのto_datetime()の引数「errors='coerce'」の使い方
    ここでは行5の列「date_added」の値を意図的に不正な値「2021-01-05X 11:29:59」にしています。 Pandasのto_datetime()メソッドの引数に「errors='coerce'」を指定したときは、「errors='ignore'」と同様エラーを無視して処理を続行します。 ただし、「errors='coerce'」のときは不正なデータは「NaT(Not a Timeの意味)」として格納します。 そして、列「date_added」のデータ型は「datetime64」になります。 print(df)でDataFrameを表示すると不正な日時は「NaT」が表示されるので、該当するデータを修正します。 「errors='coerce'」は、データ型を優先してデータ型に値を合わせます。 なのでデータ型と異なるデータを検出したときは「NaT」と置換します。 この場合、列「data_added」のデータ型「datetime64」になることが保証されます。 一方、「errors='ignore'」は、取り込むデータの型を優先します。 なので不正な日時を検出したときはデータに合わせて列「date_added」のデータ型を「object」とします。
    data = {
        'category_name': ['JavaScript','jQuery','Python','C++','C#'],
        'page_view': [100,150,900,600,700],
        'exclude_me': [False, True, False, False, False],
        'date_added': ['2021-01-05X 11:29:59','2021-02-25 12:13:15','2021-05-25 05:06:59','2020-11-09 03:05:02','2019-12-05 01:02:03']
    }
    df = pd.DataFrame(data)
    print(f'df.date_added.dtypes (before) = {df.date_added.dtypes}')
    df['date_added'] = pd.to_datetime(df['date_added'], format='%Y-%m-%d %H:%M:%S', errors='coerce') # Convert string to datetime format => NaT
    print(f'df.date_added.dtypes (after)  = {df.date_added.dtypes}')
    print()
    print(df)   # or print(df[df.date_added.isnull()])
    
    # Tip:
    # NaN is a NumPy value. np.NaN
    # NaT is a Pandas value. pd.NaT
    # None is a vanilla Python value. None
    click image to zoom!
    TIP3[1]
    TIP3の実行結果です。category_nameが「JavaScript」のdate_addedの値が「NaT」と表示されています。 「NaT」とは「Not a Time」の意味ですから値が「日時」の形式でないということがわかります。 データの件数が少ないときは「errors='ignore'」でもよいのですが、不正なデータを探すことを考える「errors='coerce'」を指定することを推奨します。
    click image to zoom!
    TIP3[2]
    DataFrameの列「date_added」の値が不正なレコード(行)のみ絞り込みしたいときは「isnull()」を使用します。 ここでは「print(df[df.date_added.isnull()])」で「NaT」のデータのみ絞り込んで表示させています。
  2. Pythonのappend()関数で列の値をリスト型変数に追加する

    行2ではDataFrameのインデックスをlist型で定義しています。 DataFrameのインデックスはデフォルトでint型になりますが、この例のようにstr型も指定できます。 行3では列名をlist型で定義しています。 行4ではlist型の空の変数を定義しています。 行5-9ではPythonのappend()関数でリストに格納されている値を変数dataに追加しています。 つまり、行5を実行するとdata変数の内容は「data = [['JavaScript', 100, False, '2021-01-05 11:29:59']]」のようになります。 行12ではPandasのDataFrame()メソッドでデータを取り込んでいます。 DataFrame()メソッドの引数1にはデータの値が格納されているlist型の変数「data」を指定します。 引数columnsには列名が格納されているlist型の変数「columns」を指定します。 引数indexにはインデックスが格納されているlist型の変数「indices」を指定します。 行11のように引数indexを省略するとデフォルトのint型のインデックスになります。 行13のように記述すると列名「date_added」を除外することができます。 行14では列名「date_added」をstr型からdatetime型に変換しています。 行15ではDataFrameの内容を表示しています。
    # Add Rows One By One To Data
    indices = ['a','b','c','d','e']
    columns = ['category_name', 'page_view', 'exclude_me', 'date_added']
    data = []
    data.append(['JavaScript', 100, False, '2021-01-05 11:29:59'])
    data.append(['jQuery', 150, True, '2021-02-25 12:13:15'])
    data.append(['Python', 900, False, '2021-05-25 05:06:59'])
    data.append(['C++', 600, False, '2020-11-09 03:05:02'])
    data.append(['C#', 700, False, '2019-12-05 01:02:03'])
    
    #df =pd.DataFrame(data, columns=columns)  
    df =pd.DataFrame(data, columns=columns, index=indices)  
    #df = pd.DataFrame.from_records(data, exclude=['date_added'], columns=columns, index=indices) 
    df['date_added'] = pd.to_datetime(df['date_added'], format='%Y-%m-%d %H:%M:%S') # Convert string to datetime format
    print(df)
    click image to zoom!
    図2
    図2が実行結果です。print(df)で表示した内容が出力されています。 インデックスがint型ではなくstr型になっています。
  3. Pythonのzip()関数で値を定義した複数の変数を一つにまとめる

    行1ではDataFrameの「index」の値を定義しています。 行2ではDataFrameの列名を定義しています。 行3-6ではそれぞれの列の値を定義しています。 行7ではPythonのzip()関数を使用して行3-6で定義した列の値をzipped_listにまとめています。 行8ではPandasのDataFrame()でデータを生成しています。 行9では「date_added」列をstr型からdatetime型に変換しています。 行10ではDataFrameのデータを表示しています。 行12-16ではDataFrameのshapeプロパティを使用して行数、列数を取得して表示しています。
    indices = ['a','b','c','d','e']
    columns = ['category_name', 'page_view', 'exclude_me', 'date_added']
    list_of_category = ['JavaScript','jQuery','Python','C++','C#']
    list_of_pageview = [100,150,900,600,700]
    list_of_excludeme = [False,True,False,False,False]
    list_of_dateadded = ['2021-01-05 11:29:59','2021-02-25 12:13:15','2021-05-25 05:06:59','2020-11-09 03:05:02','2019-12-05 01:02:03']
    zipped_list = list(zip(list_of_category, list_of_pageview, list_of_excludeme, list_of_dateadded))
    df = pd.DataFrame(zipped_list, columns=columns, index=indices)  
    df['date_added'] = pd.to_datetime(df['date_added'], format='%Y-%m-%d %H:%M:%S') # Convert string to datetime format
    print(df)
    #print(df.info())               # Display number of rows, columns, etc.
    print(f'df.shape = {df.shape}') # Get the number of rows and columns
    print(f'rows = {df.shape[0]}')  # Get the number of rows
    print(f'cols = {df.shape[1]}')  # Get the number of columns
    rows, cols = df.shape
    print(f'rows = {rows}, cols = {cols}')
    click image to zoom!
    図3
    図3は実行結果です。print()で表示したDataFrameの行数、列数が出力されています。

既存のDataFrameにレコードを追加する

  1. Append()でレコード(行)を追加する

    行2ではレコード(行)を追加する前のDataFrameを表示しています。 行3ではDataFrameに追加するレコードをdict型で定義しています。 行4ではDataFrameのappend()メソッドでレコードを追加しています。 append()でレコードを追加するときは、DataFrameのindexを0から採番させる必要があるので「ignore_index = True」を指定する必要があります。 「ignore_index = False」を指定するとエラーになります。
    # Using Append 
    print(df)
    row_dict = {'category_name': 'Liberal Arts', 'page_view': 1000, 'exclude_me': True, 'date_added': '2021-08-31 15:34:00'}
    df = df.append(row_dict, ignore_index = True)
    print(df)
    click image to zoom!
    図4
    図4は実行結果です。レコードを追加する前のDataFrameのindexは英文字の「a-e」になっています。 ところがappend()でレコードを追加後のDataFrameのindexは数字の「0-5」になっています。 これはappend()で「ignore_index = True」を指定しているのでPandasはindexを数字で自動採番しているためです。
  2. Concat()でレコード(行)を追加する

    行2-7ではdict型でレコードの列と値を定義しています。 行8ではレコードを追加する前のDataFrameを表示しています。 行9では追加するレコードをDataFrameの「df2」に生成しています。 行10ではPandasのconcat()メソッドでDataFrame(df,df2)を結合しています。 引数に「ignore_index = True」を指定してDataFrameのindexを数字の0から自動採番させています。 引数の「axis = 0」は行を追加する意味です。列を追加するときは「axis = 1」を指定します。
    # Using Concat
    row_data = {
        'category_name': ['ASP.NET'],
        'page_view': [900],
        'exclude_me': [True],
        'date_added': ['2021-08-31 15:42:00']
    }
    print(df)
    df2 = pd.DataFrame(row_data)
    df = pd.concat([df, df2], ignore_index = True, axis = 0)
    print(df)
    click image to zoom!
    図5
    図5はconcat()の引数に「ignore_index = True」を指定したときの実行結果です。 DataFrameのすべてのレコード(行)のindexが0から自動採番されています。
    click image to zoom!
    図6
    図6はconcat()の引数に「ignore_index = False」を指定したときの実行結果です。 DataFrameのindexが英文字(a-e)と数字(0)で混在しています。 concat()で追加したレコードのindexは常に数字の0から採番されます。 そして、「ignore_index = True」を指定すると追加する前のDataFrameのレコードも含めて全レコードを数字の0から自動採番します。 なので、concat()でDataFrameを連結するときは常に「ignore_index = True」を指定してください。
  3. Loc()で先頭にレコード(行)を追加する

    行2ではDataFrameの既存のindexを削除して新規のindexを0から自動採番します。 行6ではDataFrameのloc()メソッドでDataFrameの先頭にレコード(行)を追加しています。 loc()の引数にはindex番号「-1」を指定しています。 行7ではDataFrameの全レコードのindexを「+1」加算しています。 行8ではDataFrameをindexで並べ替えています。 行9ではDataFrameの内容を表示しています。
    print(df)
    df.reset_index(drop=True, inplace=True)
    print(df)
    
    # Insert Row At Top
    df.loc[-1] = ['Insert row at top', 100, True, '2021-08-30 15:55:00']
    df.index = df.index + 1    # Add index by 1
    df = df.sort_index()
    print(df)
    click image to zoom!
    図7
    図7は実行結果です。行1のprint(df)ではDataFrameのindexが英文字(a-e)で表示されています。 行3のprint(df)ではindexが数字(0-4)に変換されています。 行9のprint(df)では新規に追加したレコード(行)がDataFrameの先頭に表示されています。
  4. Loc()で終端にレコード(行)を追加する

    行3ではDataFrameのloc()メソッドでDataFrameの終端にレコード(行)を追加しています。 loc()の引数にはindex番号として「df.shape[0]」を指定しています。 DataFrameのshapeプロパティには行数と列数が格納されています。 「df.shape[0]」では行数(5)を取得しています。 つまり、loc()のindex番号として「5」を指定しています。
    # Insert Row At Bottom
    print(df)
    df.loc[df.shape[0]] = ['Insert row at bottom', 100, True, '2021-08-30 15:57:00']
    print(df)
    click image to zoom!
    図8
    図8は実行結果です。 行2のprint(df)ではレコード追加前のDataFrameを表示しています。 行4のprint(df)ではレコード追加後のDataFrameを表示しています。 DataFrameの終端にレコードが追加されたことが確認できます。
  5. Loc()で任意の位置にレコード(行)を追加する

    行3ではDataFrameのloc()メソッドで新規レコード(行)を挿入しています。 loc()の引数にはindex番号として「2.5」を指定しています。 この場合レコードはindex番号が「2」と「3」の間に挿入されます。 行4ではDataFrameのreset_index()メソッドでindexを再作成させています。 引数に「drop=True」の指定があるので既存のindexは削除されます。 さらに、DataFrameのsort_index()メソッドでレコード(行)をindex順に並べ替えています。
    # Insert a row at an arbitrary position in a datafrme
    print(df)
    df.loc[2.5] = ['Insert row at an arbitrary position', 100, True, '2021-08-30 15:57:00']
    df = df.sort_index().reset_index(drop=True)
    print(df)
    click image to zoom!
    図9
    図9は実行結果です。 行2のprint(df)ではレコード(行)を挿入前のDataFrameを表示させています。 行5のprint(df)ではレコード挿入後のDataFrameを表示させています。 レコードがindex番号2と3の間(つまり、'Python'と'C++'の間)に挿入されたことが確認できます。

既存のDataFrameに列を追加する

  1. Series.dtで「年・月・日」の列を追加する

    行3-5ではPandasのSeries.dt属性のyear, month, dayプロパティを使用してDataFrameに新規の列「year, month, day」を追加しています。
    # Add year, month, day columns
    print(df)
    df['year'] = df['date_added'].dt.year
    df['month'] = df['date_added'].dt.month 
    df['day'] = df['date_added'].dt.day
    print(df)
    click image to zoom!
    図10
    図10は実行結果です。DataFrameに列「year, month, day」が追加されています。
  2. DataFrameを列year, month, dayで絞り込む

    行2-3では列「year」が2021年のレコード(行)を絞り込んで表示しています。 行4-5では列「month」が2月~5月のレコードを絞り込んで表示しています。 行6-7では列「day」が25日のレコードを絞り込んで表示しています。
    # Filter by year, month, day
    filter_year = (df['year'] == 2021)
    print(df[filter_year])
    filter_month = (df['month'] >= 2) & (df['month'] <= 5)
    print(df[filter_month])
    filter_day = (df['day'] == 25)
    print(df[filter_day])
    click image to zoom!
    図11
    図11は実行結果です。「year」の絞り込みでは該当するレコードが3件、 「month」の絞り込みでは該当するレコードが2件、 「day」の検索では該当レコードが2件表示されています。
    click image to zoom!
    TIP4[1]
    TIP4[1]:列「date_added」でデータを絞り込むときにはDataFrameのset_index()メソッドでインデックスを作成します。 そして、DataFrameのloc()メソッドで日付(YYYY,YYYY-MM,YYYY-MM-DD)を指定します。 ここでは「2021」を指定してデータを絞り込んでいます。
    click image to zoom!
    TIP4[2]
    TIP4[2]:DataFrameのloc()メソッドでデータを絞り込むとき範囲を指定することもできます。 たとえば、2019年~2020年までの範囲で絞り込むときは「'2019':'2020'」のようにコロン(:)で区切って指定します。
    click image to zoom!
    TIP4[3]
    TIP4[3]:日付でデータを絞り込むとき年月、年月日で範囲を指定することもできます。 たとえば、2021年1月~2021年2月の範囲で絞り込むときは「'2021-01':'2021-02'」のように指定します。 2021年2月25日~2021年5月25日の範囲で絞り込むときは「'2021-02-25':'2021-05-25'」のように指定します。
  3. Series.dtで「四半期・週・曜日・閏年」の列を追加する

    行2-5ではPandasのSeries.dt属性のquarter, week, dayofweek, is_leap_yearプロパティを使用して新規の列 「quarter, week_of_year, day_of_week, is_leap_year」を追加しています。
    # Add quarter, week, day of week, leap year flag
    df['quarter'] = df['date_added'].dt.quarter
    df['week_of_year'] = df['date_added'].dt.isocalendar().week
    df['day_of_week'] = df['date_added'].dt.dayofweek
    df['is_leap_year'] = df['date_added'].dt.is_leap_year
    print(df)
    click image to zoom!
    図12
    図12は実行結果です。 DataFrameに「quarter, week_of_year, day_of_week, is_leap_year」の列が追加されています。
  4. Series.dt.map()で曜日をマッピングする

    行2-11ではSeries.dt属性のmap()メソッドを使用して曜日を数値の代わりに曜日名で表示させています。 行12-21では曜日名を3文字の省略形で表示させています。 DataFrameの列数が多いときは「print(df)」で全ての列が表示されません。 この場合すべての列を表示させるには行22のようにDataFrameのset_option()で最大列数を設定します。 もしくは、行24のように特定の列のみ表示させます。
    # Add day of week name
    dw_mapping={
        0: 'Monday', 
        1: 'Tuesday', 
        2: 'Wednesday', 
        3: 'Thursday', 
        4: 'Friday',
        5: 'Saturday', 
        6: 'Sunday'
    } 
    df['day_of_week_name']=df['date_added'].dt.weekday.map(dw_mapping)
    dw_mapping2={
        0: 'Mon', 
        1: 'Tue', 
        2: 'Wed', 
        3: 'Thu', 
        4: 'Fri',
        5: 'Sat', 
        6: 'Sun'
    } 
    df['day_of_week_shortname']=df['date_added'].dt.weekday.map(dw_mapping2)
    #pd.set_option('display.max.columns', 13)
    print(df)
    print(df[['category_name','day_of_week','day_of_week_name','day_of_week_shortname']])
    click image to zoom!
    図13
    図13は実行結果です。曜日名が表示されています。
  5. DataFrameに追加した列を削除する

    行4ではDataFrameのdrop()メソッドで追加した列を削除しています。 drop()メソッドの引数columnsには削除する列名を指定します。 ここでは「df.columns[-9:]」を指定していますが、「-1」が列の最右端になるので右端から9個の列をすべて削除することになります。 ちなみに「print(df.columns[-9:])」で表示すると「'year', 'month', 'day', 'quarter', 'week_of_year', 'day_of_week', 'is_leap_year', 'day_of_week_name', 'day_of_week_shortname'」 が返されます。引数「axis=1」は列を削除することを意味します。引数「inplace=True」は削除した結果をDataFrameに反映させます。 通常、「inplace=False」で実行して確認してみて問題がなければ「inplace=True」で実行してDataFrameに反映させます。 drop()メソッドの代わりに行3のようにiloc()メソッドでも列を削除することができます。
    # Remove added columns 
    print(df)
    #df = df.iloc[: , :-9]    # Drop last 9 columns of dataframe
    df.drop(columns=df.columns[-9:], axis=1, inplace=True) # drop last 9 columns
    print(df)
    click image to zoom!
    図14
    図14は実行結果です。右端から9個の列が削除されています。

DataFrameに取り込んだ不正なデータを見つけて修正する

  1. DataFrameに不正,無効,空の値を含むデータを取り込む

    行8-11では列に不正、無効、空の値を含むデータを定義しています。 行8では列「page_view」の値に「np.nan」を定義しています。「NaN」とは「Not a Number」の意味です。ちなみに「NaT」とは「Not a Time」の意味です。 行9では列「exclude_me」の値に「np.nan」を定義しています。 行10では列「date_added」の値に不正な日付「'2021-99-99 08:07:00'」を定義しています。 行11ではすべての列の値に「None」を定義しています。列の値に「None」を定義したときは条件によっては「NaN」に置換されます。 行12ではPandasのDataFrame()メソッドでデータを取り込んでいます。 行13では「str型」として取り込んだ日時を「datetime型」に変換しています。 Pandasのto_datetime()メソッドの引数には「 errors='coerce'」を指定して不正な日時を検出しても処理を続行させています。 Pandasは不正な日時を検出したときは「NaT」(Not a Time)と置換します。
    columns = ['category_name', 'page_view', 'exclude_me', 'date_added']
    data = []
    data.append(['JavaScript', 100, False, '2021-01-05 11:29:59'])
    data.append(['jQuery', 150, True, '2021-02-25 12:13:15'])
    data.append(['Python', 900, False, '2021-05-25 05:06:59'])
    data.append(['C++', 600, False, '2020-11-09 03:05:02'])
    data.append(['C#', 700, False, '2019-12-05 01:02:03'])
    data.append(['Invalid Page View', np.nan, False, '2021-09-04 08:06:00'])
    data.append(['Invalid Exclude Me', 999, np.nan, '2021-09-04 08:07:00'])
    data.append(['Invalid Date Added', 999, True, '2021-99-99 08:07:00'])
    data.append([None, None, None, None])
    df = pd.DataFrame(data, columns=columns)  
    df['date_added'] = pd.to_datetime(df['date_added'], format='%Y-%m-%d %H:%M:%S', errors='coerce') # Convert string to datetime format => NaT
    print(df)
    print(df.dtypes)
    click image to zoom!
    図15
    図15は実行結果です。不正、無効、空のデータは「NaN」「NaT」「None」として表示されます。 列「page_view」には不正なデータが含まれていますがデータ型が「float64」になっています。これは「NaN」は「float64型」だからです。 列「exclude_me」は本来「bool型」なのですが不正なデータが含まれているので「object型(str型)」になっています。 列「date_added」には不正な日時が含まれていますが、 Pandasのto_datetime()メソッドで変換するとき「errors='coerce'」を指定して不正な日時を「NaT」に置換しているので「datetime64型」になっています。
  2. DataFrameの不正なデータを探して件数を表示する

    行2ではDataFrameのisnull()とsum()メソッドを使用して各列ごとの不正な値の件数を表示させています。 「isnull()」には「NaN,NaT,None」が含まれます。 行3ではDataFrame中の不正な値の合計件数を表示させています。
    # Get the total number of missing values
    print(f'df.isnull().sum():\n{df.isnull().sum()}')
    print(f'df.isnull().sum().sum() = {df.isnull().sum().sum()}')
    click image to zoom!
    図16
    図16は実行結果です。 DataFrameの列毎の件数と合計件数(7)が表示されています。
  3. DataFrame中の不正データのある箇所をTrue/Falseで表示させる

    行2ではDataFrame中の不正なデータ(NaN,NaT,None)をTrue/Falseで表示させています。 「True」で表示された箇所に不正な値があります。
    # Get a Boolean mask over the data 
    print(df.isnull())
    click image to zoom!
    図17
    図17は実行結果です。 不正(NaN,NaT,None)な値の箇所が「True」で表示されています。 そして、正常な値がある箇所が「False」で表示されています。
  4. DataFrameの不正なデータを探して件数を表示する

    行2ではDataFrameのisnull()とsum()メソッドを使用して各列ごとの不正な値の件数を表示させています。 「isnull()」には「NaN,NaT,None」が含まれます。 行3ではDataFrame中の不正な値の合計件数を表示させています。
    # Get the total number of missing values
    print(f'df.isnull().sum():\n{df.isnull().sum()}')
    print(f'df.isnull().sum().sum() = {df.isnull().sum().sum()}')
    click image to zoom!
    図16
    図16は実行結果です。 DataFrameの列毎の件数と合計件数(7)が表示されています。
  5. DataFrame中の不正データのある箇所をTrue/Falseで表示させる

    行2ではDataFrame中の不正なデータ(NaN,NaT,None)をTrue/Falseで表示させています。 「True」で表示された箇所に不正な値があります。
    # Get a Boolean mask over the data 
    print(df.isnull())
    click image to zoom!
    図17
    図17は実行結果です。 不正(NaN,NaT,None)な値の箇所が「True」で表示されています。 そして、正常な値がある箇所が「False」で表示されています。
  6. 一箇所でも不正な値があるレコード(行)を削除する

    行2ではDataFrameのdropna()メソッドでレコード(行)中に一箇所でも不正なデータ(値)がある行を削除します。 dropna()の引数「axis=0」はレコード(行)を削除することを意味します。 引数「inplace=True」はDataFrameに反映させることを意味します。
    # To drop row if any NaN/NaT/None values are present
    df.dropna(axis=0, inplace=True)
    print(df)
    click image to zoom!
    図18
    行18は実行結果です。 index番号が5-8のレコード(行)中に不正な値が含まれるのでレコード(行)が削除されています。
  7. レコード(行)中の全列の値が不正なときはレコード(行)を削除する

    行2ではDataFrameのdropna()メソッドでレコード(行)中の全列の値が不正なときのみレコード(行)を削除します。 dropna()の引数「how='all'」は、全列の値が不正であることを意味します。
    # To drop row if all values are invalid
    df.dropna(axis=0, how='all', inplace=True)
    print(df)
    click image to zoom!
    図19
    図19は実行結果です。 index番号8が削除条件に該当するのでレコード(行)が削除されています。
  8. レコード(行)中の正常な値を持つ列の件数が4以下ならレコード(行)を削除する

    行2ではレコード(行)の列の値が正常な列の件数が4以下のときのみレコード(行)が削除されます。 逆に言えば、レコード(行)中に1件でも不正な値を持つ列があるときレコード(行)が削除されます。
    # To drop row if valid values are less than 4
    df.dropna(axis=0, how='any', thresh=4, inplace=True)
    print(df)
    click image to zoom!
    図20
    図20は実行結果です。 index番号5-8のレコード(行)は、正常な値を持つ列の件数が3個しかないので削除条件が適用されてレコード(行)が削除されています。
  9. レコード(行)の列(exclude_me)が不正な値のとき「False」と置換する

    行2-3ではDataFrameのfillna()メソッドでレコード(行)の列「exclude_me」の値が不正なとき「False」で置換します。 fillna()の引数「value=replace_value」では列名と置換する値を指定します。 ここでは列名「exclude_me」と値「False」を指定しています。 なお、置換する列名と値は複数指定することができます。
    # To replace all NaN values with ???
    replace_value = {'exclude_me': False}
    df.fillna(value=replace_value, inplace=True)
    print(df)
    click image to zoom!
    図21
    図21は実行結果です。 index番号6のレコードの列「exclude_me」の値が「NaN」なので条件が適用されて値が「False」で置換されています。
  10. DataFrameの複数の列の値を置換する

    行2-4ではDataFrameのfillna()メソッドでレコード(行)の複数の列の値を置換(列の値が不正なときのみ置換)しています。 行3ではレコードの列名と置換する値を定義しています。 ここでは「category_name, page_view, exclude_me, date_added」の列を置換しています。 余談ですが、行5のようにfillna()に引数「method="ffill"」を指定すると、不正な値を検出したとき直前のレコード(行)の値と置換します。 たとえば、列「exclude_me」で不正な値「NaN」を検出したとき、直線のレコードの列「exclude_me」の値と置換します。 この機能は大量のデータを置換するときに利用すると便利です。
    # To replace all NaN values with ???
    date_value = '2021-01-01 00:00:00'.format('%Y-%m-%d %H:%M:%S')
    replace_values = {'category_name':'No Category', 'page_view':0, 'exclude_me': False, 'date_added': date_value}
    df.fillna(value=replace_values, inplace=True)
    #df.fillna(method="ffill", inplace=True)
    print(df)
    print(df.dtypes)
    click image to zoom!
    図22
    図22は実行結果です。 DataFrameの全ての不正な値が置換されています。 ただし、列「page_view」のデータ型が「float64」になっています。
  11. DataFrameのデータ型を変換する

    行3ではDataFrameのastype()メソッドで列「page_view」のデータ型を「float64」から「int32」に変換しています。 int型はint, int8, int16, int32, int64のいずれかを指定できます。 「int」を指定したときPandasはデータの長さから最適なものを選択します。
    # Converting float to int
    print(df.dtypes)
    df['page_view'] = df['page_view'].astype('int')   # int8, int16, int32, int64
    print(df)
    print(df.dtypes)
    click image to zoom!
    図23
    図23は実行結果です。列「page_view」のデータ型が「int32」に変換されています。 これでDataFrameの全てのレコード(行)とデータ型がクリーンになりました。

DataFrameの列のデータ型を変更する

  1. DataFrameにテストデータをロードする

    行1-11ではテスト用のデータを定義しています。 行6で「np.nan」を使用していますがこれはNumpyの「NaN: Not a Number」の意味です。 行8では日本円の記号をユニコード(Unicode)で記述しています。 ちなみに、本記事ではコードが見やすくなるようにバックスラッシュを円の記号「\」ではなく「\」で表示させています。 行13ではPandasのDataFrame()メソッドでデータをロードしています。 行14ではDataFrameにロードしたデータを表示しています。 行15ではDataFrameの列のデータ型を表示しています。
    data = {
        'string_col': ['1','2','3','4'],
        'int_col': [1,2,3,4],
        'float_col': [1.1,1.4,1.5,1.9],
        'mix_col': [1, '9', 5, 'X'],
        'missing_col': [1.0, 2, 3, np.nan],
        'money_usd_col': ['$1,100.10','$2,200.20','$3,300.30','$4,400.40'],
        'money_yen_col': ['1,000円','2,000円',' \u00A53,300','\uFFE54,400'],
        'boolean_col': [True, False, True, True],    
        'datetime_col': ['2021-01-01 10:12:13', '2020-12-31 23:15:20', '2019-01-31 01:10:10', '2018-12-31 23-59-59']
      }
    
    df = pd.DataFrame(data)
    print(df)
    print(df.dtypes)
    click image to zoom!
    図24
    図24は実行結果です。行6で定義した「np.nan」は「NaN」のように表示されています。 行8で定義したUnicodeの円記号は半角(\)と全角(¥)で表示されています。
  2. DataFrameのstr型の列をint型に変換する

    行3ではDataFrameのastype()メソッドでobject型(str型)の列「string_col」をint型に変換しています。 astype()の引数にデータ型「int」を指定しています。 int型には、「int8, int16, int32, int64」などの種類がありますが、PandasはDataFrameのデータの内容から自動的に最適なものを選択します。 行4ではデータに数字以外の文字(X)が含まれていてもエラーにならないように処理しています。詳しい説明は後述します。
    # Converting string to int 
    print(df['string_col'])
    df['string_col'] = df['string_col'].astype('int')
    #df['string_col'] = pd.to_numeric(df['string_col'], errors='coerce').fillna(0).astype('int')
    print(df['string_col'])
    click image to zoom!
    図25
    図25は実行結果です。列「string_col」のデータ型が「object型(str型)」から「int型(int32)」に変換されています。
  3. DataFrameのstr型の列をfloat型に変換する

    行3ではDataFrameのastype()メソッドでobject型(str型)の列「string_col」をfloat型に変換しています。 astype()の引数にデータ型「float」を指定しています。 行4はstr型のデータを安全にfloat型に変換する方法です。詳しい説明は後述します。
    # Converting string to float 
    print(df['string_col'])
    df['string_col'] = df['string_col'].astype('float')
    #df['string_col'] = pd.to_numeric(df['string_col'], errors='coerce').fillna(0).astype('float')
    print(df['string_col'])
    click image to zoom!
    図26
    図26は実行結果です。列「string_col」のデータ型が「object型(str型)」から「float型(float64)」に変換されています。
  4. DataFrameのfloat型の列をint型に変換する

    行4ではDataFrameのastype()メソッドでfloat型の列「float_col」をint型のデータ型に変換しています。 astype()の引数にデータ型「int」を指定しています。 ここではfloat型からint型に変換するときに四捨五入したいのでround(0)を実行しています。 小数点以下を四捨五入したいので引数に「0」を指定しています。 小数点以下を切り捨てるときは行3のように記述します。
    # Converting float to int 
    print(df['float_col'])
    #df['float_col'] = df['float_col'].astype('int')
    df['float_col'] = df['float_col'].round(0).astype('int')
    print(df['float_col'])
    click image to zoom!
    図27
    図27は実行結果です。列「float_col」のデータ型が「float型(float64)」から「int型(int32)」に変換されています。 また、「1.5」「1.9」が「2」に切り上げされています。
  5. 数字と英文字が混在するstr型の列をint型に変換する

    DataFrameの列「mix_col」には「1, '9', 5, 'X'」のようなデータがstr型で格納されています。 先に結論を言えば、このようなケースではint型に変換するには3段階のステップが必要になります。 まずは、DataFrameのto_numeric()メソッドを使用してstr型からint型に変換します。 このとき英文字「'X'」を検出したときエラーで処理が中断しないように引数に「errors='coerce'」を指定します。 この場合英文字「'X'」は「NaN: Not a Numberの意味」に置換されて格納されます。 「NaN」はint型に変換できないのでDataFrameのfillna()メソッドで「NaN」の値を「0」に置換します。 これですべてのデータがクリーンになったので最後にDataFrameのastype()メソッドでint型に変換します。
    # Converting a column of mixed types (include '9','X')
    print(df['mix_col'])  # mix_col': [1, '9', 5, 'X']
    #df['mix_col'] = df['mix_col'].astype('int')
    #df['mix_col'] = pd.to_numeric(df['mix_col'], errors='coerce')
    df['mix_col'] = pd.to_numeric(df['mix_col'], errors='coerce').fillna(0).astype('int')
    print(df['mix_col'])
    click image to zoom!
    図28
    図28は実行結果です。データ型が「object型(str型)」から「int型(int32)」に変換されています。 英文字「'X'」は、「NaN」に変換されて、さらに「0」に変換されています。
  6. 値が欠落(NaN)している列のデータ型をint型に変換する

    欠落(NaN)している値が含まれる列をint型に変換するには3段階で行う必要があります。 まずは、Pandasのto_number()メソッドで列の値をint型に変換します。 この時、変換中にエラーになるのを回避するために「 errors='coerce'」を指定します。 to_number()はint型に変換できないデータを検出したときは「NaN」と置換します。 ちなみに「NaN」はfloat型のデータとして格納されます。 次に、DataFrameのfillna()メソッドで「NaN」の値をint型の「0」に置換します。 そして最後に、DataFrameのastype()メソッドでint型に変換します。 行5では、これら一連の処理を行っています。
    # Handling missing values, converting to int
    print(df['missing_col'])  # 'missing_col': [1.0, 2, 3, np.nan]
    #df['missing_col'] = df['missing_col'].astype('int')
    #df['missing_col'] = pd.to_numeric(df['missing_col'], errors='coerce').astype('int')
    df['missing_col'] = pd.to_numeric(df['missing_col'], errors='coerce').fillna(0).astype('int')
    print(df['missing_col'])
    click image to zoom!
    図29
    図29は実行結果です。「NaN」の値が「0」に置換されて列がfloat型(float64)からint型(int32)に変換されています。
  7. str型の金額が格納されている列をfloat型に変換する

    str型の金額に通貨記号「$」やコンマ「,」が含まれるときは2段階でfloat型に変換する必要があります。 まずは、str型の金額から「$」と「,」を除去します。 そしてstr型の数字をfloat型に変換します。 行3ではreplace()メソッドでstr型の金額から「$」と「,」を除去しています。 行4ではto_number()メソッドでstr型の金額をfloat型に変換しています。
    # Converting a money column to numbers (USD)
    print(df['money_usd_col'])  # 'money_usd_col': ['$1,100.10','$2,200.20','$3,300.30','$4,400.40']
    df['money_usd_replace'] = df['money_usd_col'].str.replace('$', '').str.replace(',','')
    df['money_usd_replace'] = pd.to_numeric(df['money_usd_replace'])
    print(df['money_usd_replace'])
    click image to zoom!
    図30
    図30は実行結果です。str型の金額から「$」と「,」が除去されてfloat型(float64)に変換されています。
  8. 正規表現を使用してstr型の金額が格納されている列をfloat型に変換する

    行1ではreモジュールを取り込んでいます。 行2-4ではconvert_money()関数を定義しています。 行3ではreモジュールのsub()関数を使用して変数valueに格納されている文字列を置換しています。 re.sub()では引数1に正規表現パターン、引数2に置換先文字列、引数3に処理対象の文字列を指定します。 ここでは引数1に「[\円\u00A5\uFFE5\£\$\,]」、引数2に「''」、引数3に「value」を指定しています。 この正規表現では変数valueに格納されている文字列から「円」「\」「¥」「£」「$」「,」を除去しています。 つまり、str型の金額から金額の記号とコンマを除去していることになります。 行4ではPythonのfloat()関数でstr型の金額をfloat型に変換しています。 行7ではDataFrameのapply()メソッドからconvert_money()関数を呼び出しています。 行2のconvert_money()関数の引数valueにはDataFrameの列「money_usd_col」の値が格納されます。 この関数からはstr型の金額がfloat型に変換されて返されます。 float型に変換された金額は列「money_usd_replace」に格納されます。
    import re
    def convert_money(value):  
      value =  re.sub(r'[\円\u00A5\uFFE5\£\$\,]', '', value)
      return float(value)
    
    print(df['money_usd_col'])  # 'money_usd_col': ['$1,100.10','$2,200.20','$3,300.30','$4,400.40']
    df['money_usd_replace'] = df['money_usd_col'].apply(convert_money)
    print(df['money_usd_replace'])
    click image to zoom!
    図31
    図31は実行結果です。str型の金額がfloat型(float64)に変換されて列「money_usd_replace」に格納されています。
  9. lambda(ラムダ)式を使用してstr型の金額が格納されている列をfloat型に変換する

    ここではPythonのlambda式(無名関数)を使用してstr型の金額をfloat型に変換しています。 Pythonの関数と比較するとかなりスッキリした形でコードを記述することができます。 行2ではDataFrameのapply()メソッドの引数にlambda式を指定しています。 このlambda式では、列「money_yen_col」に格納されているstr型の金額から金額の記号「円,¥,\」とコンマ「,」を除去してfloat型に変換しています。 float型に変換された金額は列「money_yen_replace」に格納されます。
    print(df['money_yen_col'])  # 'money_yen_col': ['1,000円','2,000円',' \u00A53,300','\uFFE54,400'],
    df['money_yen_replace'] = df['money_yen_col'].apply(lambda v: re.sub(r'[\円\u00A5\uFFE5\£\$\,]', '', v)).astype('float')
    print(df['money_yen_replace'])
    click image to zoom!
    図32
    図32は実行結果です。列「money_yen_col」に格納されているstr型の金額がfloat型(float64)に変換されて列「money_yen_replace」に格納されています。