90786


### 前提

pandas の電話番号の列を正規表現にしたいです。  
現在下記の読み込みcsvのように先頭の0が抜けたりハイフンなしがあります。  
国際番号も含まれています。

読み込みcsv  
※テスト電話番号です。

[code]

    電話番号
    8012345678
    9012345678
    90-1234-5678
    080-1234-5678
    110000000
    06-1234-5678
    (0)0 812345678
    85212345678
    (81)801 2345678
    (852)123 45678
    
[/code]

### 実現したいこと

希望としては、下記のように正規表現で電話番号の列を置換したいです。

・国内の固定電話  
03-○○○○-○○○○  
045-○○○-○○○○  
0456-○○-○○○○  
04567-○-○○○○  
0120-○○○-○○○

・携帯電話  
090-○○○○-○○○○  
080-○○○○-○○○○  
070-○○○○-○○○○

・国際電話番号  
(81)080-○○○○-○○○○もしくは81-080-○○○○-○○○○  
(852)-○○○○-○○○○もしくは852-○○○○-○○○○

[code]

    電話番号
    080-1234-5678
    090-1234-5678
    090-1234-5678
    080-1234-5678
    110000000
    06-1234-5678
    08-1234-5678
    (852)1234-5678
    (81)080-1234-5678
    (852)1234-5678
    
[/code]

※110000000 桁数が多い電話番号に関して無視してそのままで記載したいです。  
※正規表現でできない電話番号もそのまま無視してそのままで記載したいです。

正規表現のエラーメッセージが表示されます。

[code]

    エラーメッセージ
    Traceback (most recent call last):
      File "c:\Users\test.py", line 21, in <module>
        df['電話番号'] = df['電話番号'].replace(re.compile(pattern), df['電話番号'], regex=True)
    
    ValueError: Series.replace cannot use dict-value and non-None to_replace
    
[/code]

**コード**

[code]

    import pandas as pd
    import re
    
    #ファイル名
    filename1=r"C:\Users\test1.csv"
    
    #csv読み込みdtype=objectと指定
    df = pd.read_csv(filename1,dtype=object)
    
    #列指定　タイプ確認
    print(df['電話番号'])
    
    #先頭に0を追加
    df['電話番号'] = '0' + df['電話番号'].astype(str)
    print(df)
    
    #電話番号正規表現
    pattern = r'[\(]{0,1}[0-9]{2,4}[\)\-\(]{0,1}[0-9]{2,4}[\)\-]{0,1}[0-9]{3,4}'
    
    #正規表現で列置換　ハイフンを追加
    df['電話番号'] = df['電話番号'].replace(re.compile(pattern), df['電話番号'], regex=True)
    print(df)
    
    #csv保存
    df.to_csv(filename1,encoding='utf_8_sig',index=False)
    
    
[/code]

### 試したこと

上記のコードで試してみました。わかりづらいところも多いと思いますが、正規表現のエラーが表示されます。

参考ページ  
[pythonで電話番号を正規表現で](https://qiita.com/yousuke_yamaguchi/items/422148e93eaf010d3532)

希望通りのように正規表現で電話番号の列で変換可能でしょうか。

原因が思い当たる方はご教授いただけませんでしょうか。

よろしくお願いします。


CC BY-SA 4.0
2022-08-27T03:19:16.990
90780
2022-08-29T15:29:22.957
2022-08-27T04:15:35.277
3060
18859
question
python python3 pandas 正規表現
正規表現で電話番号の文字列を正規化したい


電話番号をどのように正規化したいかが分かっているならば[`Series`クラスの`apply`メソッド](https://pandas.pydata.org/docs/reference/api/pandas.Series.apply.html)を使ってみてはいかがでしょうか？正規化のルールをすべて把握してないので、国内携帯番号だけについて書くと次のようになります。

[code]

    import pandas as pd
    import re
    import io
    
    text = '''
    8012345678
    9012345678
    90-1234-5678
    080-1234-5678
    110000000
    06-1234-5678
    (0)0 812345678
    85212345678
    (81)801 2345678
    (852)123 45678
    '''
    df = pd.read_csv(io.StringIO(text), names=['電話番号'])
    
    
    def normalize_number_string(x):
        # y: xから先頭の0を取って、さらにハイフンをすべて取り除いたもの
        y = x
        y = y.lstrip('0')
        y = y.replace('-', '')
    
        # 7、8、9から始まる10桁の数字は、国内の携帯番号とみなして整形する。
        if re.match(r'^[789]\d{9}$', y):
            return f'0{y[:2]}-{y[2:6]}-{y[6:]}'
    
        # 必要ならば、ここに他の変換規則を追加する。
        ...
    
        # 形式不明であれば、もとの値を返す。
        return x
    
    
    df['電話番号'] = df['電話番号'].apply(normalize_number_string)
    
    print(df)
    
[/code]

[code]

                  電話番号
    0    080-1234-5678
    1    090-1234-5678
    2    090-1234-5678
    3    080-1234-5678
    4        110000000
    5     06-1234-5678
    6   (0)0 812345678
    7      85212345678
    8  (81)801 2345678
    9   (852)123 45678
    
[/code]

なお、出ているエラーについてですが、[`Series`クラスの`replace`メソッド](https://pandas.pydata.org/docs/reference/api/pandas.Series.replace.html)は第1引数`to_replace`に置換対象を、第2引数`value`に置換後の値を指定します。第1引数を正規表現で与えるなら、第2引数もそれに応じた形にする必要があります（[`re.sub`](https://docs.python.org/ja/3/library/re.html#re.sub)の第1、第2引数と同じ）。そこに`df['電話番号']`を渡しているのでpandasがエラーを出しています（正規表現にエラーがあるわけではない）。

また、参考にされているページは、正規表現によって電話番号を検索して抜き出すことを目的としています。正規表現によって電話番号を正規化することを目的としていません。`Series.replace()`に使うのであれば（そして1度で済ませようとするなら非常に巧みに）グループを設定した正規表現を与える必要があります。

**libphonenumber**

電話番号をハイフンが入った形に正規表現を使って正規化するのは（世界中の国別・市外・市内局番の仕様をすべて網羅するのが個人の力では）無理なので[Googleのlibphonenumber](https://github.com/google/libphonenumber)の[Pythonラッパー](https://github.com/daviddrysdale/python-
phonenumbers)を使うことにすると、`normalize_number_string`は以下のようになります。

[code]

    def normalize_number_string(x):
        """電話番号xを正規化して返す。"""
        y = str(x)
    
        # 国際電話番号だと仮定して解釈してみる。うまくいったらそれを返す。
        try:
            z = phonenumbers.parse(f'+{y}', None)
            if phonenumbers.is_possible_number(z):
                if z.country_code == 81:  # 日本
                    y = phonenumbers.format_number(z, phonenumbers.PhoneNumberFormat.NATIONAL)
                    return y
                y = phonenumbers.format_number(z, phonenumbers.PhoneNumberFormat.INTERNATIONAL)
                # ここでyがINTERNATIONAL形式になっていることに注意
                y = re.sub(r'^\+(\d+) ', r'(\1)', y)  # 国番号をカッコでくくる
                y = y.replace(' ', '-')  # 空白ではなくハイフンを区切りに
                return y
        except phonenumbers.NumberParseException:
            pass
    
        # 国内電話番号だと仮定して解釈してみる。うまくいったらそれを返す。
        try:
            z = phonenumbers.parse(y, 'JP')
            if phonenumbers.is_possible_number(z):
                y = phonenumbers.format_number(z, phonenumbers.PhoneNumberFormat.NATIONAL)
                return y
        except phonenumbers.NumberParseException:
            pass
    
        # 形式不明であれば、もとの値を返す。
        return x
    
[/code]

[code]

                電話番号
    0  080-1234-5678
    1  090-1234-5678
    2  090-1234-5678
    3  080-1234-5678
    4   011-000-0000
    5   06-1234-5678
    6  0081-234-5678
    7  (852)12345678
    8  080-1234-5678
    9  (852)12345678
    
[/code]


CC BY-SA 4.0
2022-08-27T05:53:38.377
90786
2022-08-29T15:29:22.957
2022-08-29T15:29:22.957
36010
36010
90780
answer