问题如题。一般pandas读取数据的时候,会自动检测数据的类型,但有时候可能不是特别准确,还需要我们自己做类型转换。比如下面这种:

a = [['a', '1.2', '4.2'], ['b', '70', '0.03'], ['x', '5', '0']]
df = pd.DataFrame(a)

df.dtypes
# 0    object
# 1    object
# 2    object
# dtype: object

如何修改第2、3列的类型?扩展一下,如果有很多列的时候,如何高效的修改?

Pandas中主要有4种类型转换相关的方法:

  1. to_numeric/to_datetime/to_timedelta:可以参数转换为合适的对应类型。
  2. astype
  3. infer_objects
  4. convert_dtypes

to_xxx

to_numeric

to_numeric将参数转换为合适的数值类型(float64/int64)。签名如下:

pandas.to_numeric(arg, errors='raise', downcast=None)

先看一些使用例子:

In [2]: s = pd.Series(["8", 6, "7.5", 3, "0.9"])

In [3]: s
Out[3]:
0      8
1      6
2    7.5
3      3
4    0.9
dtype: object

In [4]: pd.to_numeric(s)
Out[4]:
0    8.0
1    6.0
2    7.5
3    3.0
4    0.9
dtype: float64

可以使用apply()方法批量转换DataFrame里面的列:

In [11]: df = pd.DataFrame([['1', '2', '3'],['4', '5', '6'],['7.1', '8.0', '9']], columns=['a','b', 'c'])

In [12]: df
Out[12]:
     a    b  c
0    1    2  3
1    4    5  6
2  7.1  8.0  9

In [13]: df.dtypes
Out[13]:
a    object
b    object
c    object
dtype: object

In [14]: df_1=df.apply(pd.to_numeric)

In [15]: df_1
Out[15]:
     a    b  c
0  1.0  2.0  3
1  4.0  5.0  6
2  7.1  8.0  9

In [16]: df_1.dtypes
Out[16]:
a    float64
b    float64
c      int64
dtype: object

也可以只对某些列进行转换:

In [18]: df[['a','b']]=df[['a','b']].apply(pd.to_numeric)

In [19]: df
Out[19]:
     a    b  c
0  1.0  2.0  3
1  4.0  5.0  6
2  7.1  8.0  9

In [20]: df.dtypes
Out[20]:
a    float64
b    float64
c     object
dtype: object

类型转换难免会产生错误,比如无法转换等,to_numeric提供了一个参数errors来让用户控制发生错误时如何处理,用有三个选项:

  • 'raise':默认值,即抛出异常
  • 'ignore':转换失败时,保留原值
  • 'coerce':转换失败时,设置为NaN

看一些例子:

In [21]: df=pd.DataFrame([['1','2'],['3','4'],['5','s']], columns=['a','b'])

In [22]: df
Out[22]:
   a  b
0  1  2
1  3  4
2  5  s

In [23]: df.dtypes
Out[23]:
a    object
b    object
dtype: object

In [24]: df.apply(pd.to_numeric)
ValueError: Unable to parse string "s" at position 2

In [25]: df.apply(pd.to_numeric, errors='coerce')
Out[25]:
   a    b
0  1  2.0
1  3  4.0
2  5  NaN

In [26]: df.apply(pd.to_numeric, errors='ignore')
Out[26]:
   a  b
0  1  2
1  3  4
2  5  s

to_numeric默认会转换为float64或者int64,如果你想节省内存转换为小一些的类型的话,可以使用to_numeric提供的downcast参数,可选值如下:

  • 'integer'或者'signed':转换为np.int8
  • 'unsigned':转换为np.uint8
  • 'float':转换为np.float32

看一些例子:

In [29]: s = pd.Series(['1','2','-7'])

In [30]: s
Out[30]:
0     1
1     2
2    -7
dtype: object

In [31]: pd.to_numeric(s)
Out[31]:
0    1
1    2
2   -7
dtype: int64

In [32]: pd.to_numeric(s, downcast='integer')
Out[32]:
0    1
1    2
2   -7
dtype: int8

# 注意这里:因为 unsigned无法表示-7,所以这里实际没有发生downcast
In [33]: pd.to_numeric(s, downcast='unsigned')
Out[33]:
0    1
1    2
2   -7
dtype: int64

In [34]: pd.to_numeric(s, downcast='float')
Out[34]:
0    1.0
1    2.0
2   -7.0
dtype: float32

这里有2个注意点:

  1. downcast是发生在核心的类型转换之后的(也就是先将原始类型转换为float64/int64,然后再执行downcast动作),所以前面介绍的那个errors参数对downcast这里是无效的。
  2. 如果目标类型无法容纳被转换的值,就不会发生实际的转换。比如上面尝试转换为'unsigned'类型时,因为-7无法转换为unsigned,所以实际没有执行downcast。

    to_datetime

    to_datetime把参数转换为datetime类型,相比于to_numeric,函数原型复杂了一些。

    pandas.to_datetime(arg, errors='raise', dayfirst=False, yearfirst=False, utc=None, format=None, exact=True, unit=None, infer_datetime_format=False, origin='unix', cache=True)

    看一些使用例子:

    # 可以使用这些关键字来构造表示时间日期的字典:[‘year’, ‘month’, ‘day’, ‘minute’, ‘second’, ‘ms’, ‘us’, ‘ns’]),复数也可以
    In [35]: df = pd.DataFrame({'year': [2015, 2016], 'month': [2, 3], 'day':[4, 5]})
    
    In [36]: df
    Out[36]:
  3. 2015 2 4
  4. 2016 3 5

    In [37]: df.dtypes
    Out[37]:
    year int64
    month int64
    day int64
    dtype: object

    In [38]: pd.to_datetime(df)
    Out[38]:

  5. 2015-02-04
  6. 2016-03-05
    dtype: datetime64[ns]

    In [40]: pd.to_datetime(1490195805, unit='s')
    Out[40]: Timestamp('2017-03-22 15:16:45')

    In [41]: pd.to_datetime(1490195805433502912, unit='ns')
    Out[41]: Timestamp('2017-03-22 15:16:45.433502912')

    In [42]: pd.to_datetime("10/11/12", dayfirst=True)
    Out[42]: Timestamp('2012-11-10 00:00:00')

    In [43]: pd.to_datetime("10/11/12", yearfirst=True)
    Out[43]: Timestamp('2010-11-12 00:00:00')

    In [44]: pd.to_datetime("10/11/12", dayfirst=True, yearfirst=True)
    Out[44]: Timestamp('2010-12-11 00:00:00')

    `errors`字段含义同`to_numeric`:
  • 'raise':默认值,即抛出异常
  • 'ignore':转换失败时,保留原值
  • 'coerce':转换失败时,设置为NaT。这里注意一种情况就是Pandas的时间精度是纳秒,且用int64表示,大概只能表示584年这样一个范围([pd.Timestamp.min, pd.Timestamp.max]),当转换的时间超出这个范围时也算失败。

看个例子:

In [46]: pd.Timestamp.min
Out[46]: Timestamp('1677-09-21 00:12:43.145225')

In [47]: pd.Timestamp.max
Out[47]: Timestamp('2262-04-11 23:47:16.854775807')

In [48]: pd.to_datetime('13000101', format='%Y%m%d', errors='raise')
OutOfBoundsDatetime: Out of bounds nanosecond timestamp: 1300-01-01 00:00:00

In [49]: pd.to_datetime('13000101', format='%Y%m%d', errors='ignore')
Out[49]: datetime.datetime(1300, 1, 1, 0, 0)

In [50]: pd.to_datetime('13000101', format='%Y%m%d', errors='coerce')
Out[50]: NaT

In [53]: pd.to_datetime('130000101', format='%Y%m%d', errors='ignore')
Out[53]: '130000101'

to_timedelta

timedelta类型表示两个时间的绝对差值,to_timedelta将参数转换为timedelta类型。方法签名如下:

pandas.to_timedelta(arg, unit=None, errors='raise')

unit用于指定参数的单位,默认为ns,合法的取值如下:

  • ‘W’
  • ‘D’ / ‘days’ / ‘day’
  • ‘hours’ / ‘hour’ / ‘hr’ / ‘h’
  • ‘m’ / ‘minute’ / ‘min’ / ‘minutes’ / ‘T’
  • ‘S’ / ‘seconds’ / ‘sec’ / ‘second’
  • ‘ms’ / ‘milliseconds’ / ‘millisecond’ / ‘milli’ / ‘millis’ / ‘L’
  • ‘us’ / ‘microseconds’ / ‘microsecond’ / ‘micro’ / ‘micros’ / ‘U’
  • ‘ns’ / ‘nanoseconds’ / ‘nano’ / ‘nanos’ / ‘nanosecond’ / ‘N’

errors的取值同to_datetime。看几个使用例子:

In [55]: pd.to_timedelta('1 days 06:05:01.00003')
Out[55]: Timedelta('1 days 06:05:01.000030')

In [56]: pd.to_timedelta('15.5us')
Out[56]: Timedelta('0 days 00:00:00.000015500')

In [57]: pd.to_timedelta(['1 days 06:05:01.00003', '15.5us', 'nan'])
Out[57]: TimedeltaIndex(['1 days 06:05:01.000030', '0 days 00:00:00.000015500', NaT], dtype='timedelta64[ns]', freq=None)

In [58]: import numpy as np

In [59]: pd.to_timedelta(np.arange(5), unit='s')
Out[59]:
TimedeltaIndex(['0 days 00:00:00', '0 days 00:00:01', '0 days 00:00:02',
                '0 days 00:00:03', '0 days 00:00:04'],
               dtype='timedelta64[ns]', freq=None)

In [60]: pd.to_timedelta(np.arange(5), unit='d')
Out[60]: TimedeltaIndex(['0 days', '1 days', '2 days', '3 days', '4 days'], dtype='timedelta64[ns]', freq=None)

astype

astype方法可以做任意类型的转换(当然未必能成功)。方法原型如下:

pd.DataFrame.astype(dtype, copy=True, errors='raise')

dtype就是我们想要转换成的目标类型,可以使用Numpy的类型、Python的部分类型、Pandas特有的类型。copy表示是否修改原数据。errors可以取'raise'(失败是抛异常)或者'ignore'(失败时忽略并返回原值)。

看一些例子:

In [62]: df = pd.DataFrame({'col1': [1, 2], 'col2': [3, 4]})

In [63]: df.dtypes
Out[63]:
col1    int64
col2    int64
dtype: object

In [64]: df.astype('int32').dtypes
Out[64]:
col1    int32
col2    int32
dtype: object

In [65]: s = pd.Series([1, 3], dtype='int32')

In [66]: s
Out[66]:
0    1
1    3
dtype: int32

In [67]: s.astype('int64')
Out[67]:
0    1
1    3
dtype: int64

In [68]: s.astype('category')
Out[68]:
0    1
1    3
dtype: category
Categories (2, int64): [1, 3]

In [77]: s_date = pd.Series(['20220101', '20220102', '20220103'])

In [78]: s_date
Out[78]:
0    20220101
1    20220102
2    20220103
dtype: object

In [79]: s_date.astype('datetime64')
Out[79]:
0   2022-01-01
1   2022-01-02
2   2022-01-03
dtype: datetime64[ns]

In [81]: i = pd.Series([1,2,3])

In [82]: i
Out[82]:
0    1
1    2
2    3
dtype: int64

In [83]: i.astype(str)
Out[83]:
0    1
1    2
2    3
dtype: object

In [84]: s = pd.Series([1,2,-7])

In [85]: s.astype(np.int8)
Out[85]:
0    1
1    2
2   -7
dtype: int8

In [86]: s.astype(np.uint8)
Out[86]:
0      1
1      2
2    249
dtype: uint8

使用astype的时候,要注意范围,比如下面的转换不会报错,但不是我们想要的:

In [95]: s = pd.Series([1,2,-7])

# -7被转换为249
In [96]: s.astype(np.uint8)
Out[96]:
0      1
1      2
2    249
dtype: uint8

# 使用to_numeric就不会有问题
In [97]: pd.to_numeric(s, downcast='unsigned')
Out[97]:
0    1
1    2
2   -7
dtype: int64

infer_objects

pandas 0.21.0版本加入,会尝试推测类型。看个例子:

In [103]: df = pd.DataFrame({'a': [7, 1, 5], 'b': ['3','2','1']}, dtype='object')

In [104]: df.dtypes
Out[104]:
a    object
b    object
dtype: object

In [105]: df.infer_objects().dtypes
Out[105]:
a     int64
b    object
dtype: object

可以看到,infer_objects其实还是比较“肤浅”的,如果要将'b'列也转换成数值型,可以使用前面介绍的方法。

convert_dtypes

convert_dtypes会尝试将各列转换为最可能的类型,其特点是支持pd.NA,方法签名如下:

DataFrame.convert_dtypes(infer_objects=True, convert_string=True, convert_integer=True, convert_boolean=True, convert_floating=True)

通过各个类型参数可以控制某些类型是否转换;infer_objects为True时会尝试转换object类型为更具体的类型。看一些例子:

In [110]: df = pd.DataFrame(
     ...:   {
     ...:     'a': pd.Series([1, 2, 3], dtype=np.dtype('int32')),
     ...:     'b': pd.Series(['x', 'y', 'z'], dtype=np.dtype('O')),
     ...:     'c': pd.Series([True, False, np.nan], dtype=np.dtype('O')),
     ...:     'd': pd.Series(['h', 'i', np.nan], dtype=np.dtype('O')),
     ...:     'e': pd.Series([10, np.nan, 20], dtype=np.dtype('float')),
     ...:     'f': pd.Series([np.nan, 100.5, 200], dtype=np.dtype('float')),
     ...:   }
     ...: )

In [111]: df
Out[111]:
   a  b      c    d     e      f
0  1  x   True    h  10.0    NaN
1  2  y  False    i   NaN  100.5
2  3  z    NaN  NaN  20.0  200.0

In [112]: df.dtypes
Out[112]:
a      int32
b     object
c     object
d     object
e    float64
f    float64
dtype: object

In [113]: dfn = df.convert_dtypes()

In [114]: dfn
Out[114]:
   a  b      c     d     e      f
0  1  x   True     h    10    NaN
1  2  y  False     i  <NA>  100.5
2  3  z   <NA>  <NA>    20  200.0

In [115]: dfn.dtypes
Out[115]:
a      Int32
b     string
c    boolean
d     string
e      Int64
f    float64
dtype: object

End, that's all!

refs: StackOverflow: Change column type in pandas