データフレームのHEX(16進数)カラムを自動判定、10進数に自動変換

## はじめに
分析対象データにHEX（16進数）データがある場合、基本統計量や傾向を確認するためには、10進数に変換しなければなりません。
できればPythonで、HEXだけのデータカラムを自動で判定し、10進数に自動変換させたいということで、実行してみました。


## 実行条件など
>- **Google colab**で実行

 
## まずはデータを準備

16進数⇒10進数変換を試行するデータを仮想、以下の通りデータフレームに格納しました。
aはint、cはfloat、b,fがHEX(16進数)データ、dはobject、eはint（ほぼ欠損データ）としています。
16進数⇒10進数変換したいカラムは b列,f列です。

```Python:
df = pd.DataFrame({
  'a': [5, 2, 3, 100, 110, 200],
  'b': ['abc', '0C3A', '9F07D2','ffff','ffff','7b2'],
  'c': [19.3, 22.7, 99.5, 200.2, 300.5, 1200.3],
  'd': ['A', '100', 'a','abc','AB','ZA-20'],
  'e': [1, np.nan, np.nan, np.nan, np.nan, np.nan],
  'f': ['abc', '0C3A', '9F07D2','ffff','ffff','7b2']
})
print(df)
```
|index|a|b|c|d|e|f|
|---|---|---|---|---|---|---|
|0|5|abc|19\.3|A|1\.0|abc|
|1|2|0C3A|22\.7|100|NaN|0C3A|
|2|3|9F07D2|99\.5|a|NaN|9F07D2|
|3|100|ffff|200\.2|abc|NaN|ffff|
|4|110|ffff|300\.5|AB|NaN|ffff|
|5|200|7b2|1200\.3|ZA-20|NaN|7b2|


## HEX⇒INT処理をデータフレーム全体に実行すると？

```Python:
def hex_to_int(s):
  try:
    return int(s, base=16)
  except:
    return s

dfx = df.applymap(hex_to_int)
dfx
```
**元データ**
|index|a|b|c|d|e|f|
|---|---|---|---|---|---|---|
|0|5|abc|19\.3|A|1\.0|abc|
|1|2|0C3A|22\.7|100|NaN|0C3A|
|2|3|9F07D2|99\.5|a|NaN|9F07D2|
|3|100|ffff|200\.2|abc|NaN|ffff|
|4|110|ffff|300\.5|AB|NaN|ffff|
|5|200|7b2|1200\.3|ZA-20|NaN|7b2|

**変換後データ**
|index|a|b|c|d|e|f|
|---|---|---|---|---|---|---|
|0|5|2748|19\.3|**10**|1\.0|2748|
|1|2|3130|22\.7|**256**|NaN|3130|
|2|3|10422226|99\.5|**10**|NaN|10422226|
|3|100|65535|200\.2|**2748**|NaN|65535|
|4|110|65535|300\.5|**171**|NaN|65535|
|5|200|1970|1200\.3|ZA-20|NaN|1970|

上記が変換結果です。
b,f列は意図通り10進数に変換され、int, float列はそのまま維持されていますが、**d列（object）の ’A’、’100’、’abc’等がHEX（16進数）と認識され、変換されてしまいます。**


## HEX⇒INT変換するカラムを指定してみる

上記のままではまずいので、10進数に変換するカラムを指定して実行してみます。
```Python:#カラムの指定（※ #@param {type:"raw"} はGoogleColabのフォームです）
Object_label_to = 'b','f' #@param {type:"raw"}
```
```Python:#指定カラムHEX⇒INT変換
for i in Object_label_to:
  if df[col].dtype == 'object':
    df[i]=df[i].apply(lambda x:int(x, 16))
print(df)
```
|index|a|b|c|d|e|f|
|---|---|---|---|---|---|---|
|0|5|2748|19\.3|A|1\.0|2748|
|1|2|3130|22\.7|100|NaN|3130|
|2|3|10422226|99\.5|a|NaN|10422226|
|3|100|65535|200\.2|abc|NaN|65535|
|4|110|65535|300\.5|AB|NaN|65535|
|5|200|1970|1200\.3|ZA-20|NaN|1970|

これで、意図した変換ができました。
　

## string.hexdigits を利用してみる

ただ、カラムの指定はデータフレームサイズが大きくなるほど面倒なので、カラムデータが16進数であるかどうかを判定してみます。
以下を実行すると、16進数の要素を返してくれます。
```Python:
import string
string.hexdigits
```
>**0123456789abcdefABCDEF**


　
このstringを利用して、データフレームの各カラムが16進数データかどうかを確認してみます。

```Python:
for col in df.columns:
  if df[col].dtype == 'object':   #int,floatカラムに実行するとエラーになるのでobject指定
    print(col)
    result=df[col].map(lambda x: set(x).issubset(set(string.hexdigits)))
    print(result)
```
>b
0    True
1    True
2    True
3    True
4    True
5    True
Name: b, dtype: bool
d
0     True
1     True
2     True
3     True
4     True
5    **False**
Name: d, dtype: bool
f
0    True
1    True
2    True
3    True
4    True
5    True
Name: f, dtype: bool

上記は、object列であるb,d,f列のみ、データが16進数であるかが判定された結果です。
d列のNo.5データ（ZA-20）のみ、False（16進数ではない）と判定されています。
　
　
次にカラム全体が16進数データであるかを判定します。
```Python:
for col in df.columns:
  if df[col].dtype == 'object':
    if all(df[col].apply(lambda x: set(x).issubset(set(string.hexdigits)))) ==True:
      print(col,True)
    else:
      print(col,False)
```
>b True
d **False**
f True

これで、16進数だけで構成されているb列、f列だけをピックアップすることができました。


## 16進数だけのカラムデータを自動判定し、10進数に変換
```Python:
for col in df.columns:
  if df[col].dtype == 'object':
    if all(df[col].apply(lambda x: set(x).issubset(set(string.hexdigits)))) ==True:
      df[col]=df[col].apply(lambda x:int(x, 16))
print(df)
```
|index|a|b|c|d|e|f|
|---|---|---|---|---|---|---|
|0|5|2748|19\.3|A|1\.0|2748|
|1|2|3130|22\.7|100|NaN|3130|
|2|3|10422226|99\.5|a|NaN|10422226|
|3|100|65535|200\.2|abc|NaN|65535|
|4|110|65535|300\.5|AB|NaN|65535|
|5|200|1970|1200\.3|ZA-20|NaN|1970|

いけました。

ところが、現実データを確認するとあまりにゴミデータが多い。。。
**16進数だけのカラムデータ**というのは、ありえない気がしてきましたので、結局、16進数のカラムを指定し、16進数データのみ10進数に変換することに。


## 16進数のカラムを指定し、16進数データのみ10進数に変換

```Python:
HEX2INT = 'b1', 'f1' # @param {type: "raw"}
HEX2INT = pd. Series(HEX2INT)

import string
for i in HEX2INT:
  if df[i].dtype == 'object':
     for x in df[i]:
       try:
         if set (x).issubset(set(string.hexdigits)) ==True
         df[i].replace(x, int(x, 16), inplace=True)
       except:
         print('例外',i,x)
```

‘abc’,‘---’,‘fail’,‘ffff’,‘NaN’,‘7b2’というデータならば、
‘2738’,‘---’,‘fail’,‘65535’,‘NaN’,‘1970’となります。
isdigitで数字以外を nanにするか、削除するかなど、もう一段処理は必要ですが。

## 参考サイト

https://terukizm.hatenablog.com/entry/20120614/1339667254