Remap giá trị trong cột gấu trúc với một dict


318

Tôi có một từ điển giống như thế này: di = {1: "A", 2: "B"}

Tôi muốn áp dụng nó cho cột "col1" của khung dữ liệu tương tự như:

     col1   col2
0       w      a
1       1      2
2       2    NaN

để có được:

     col1   col2
0       w      a
1       A      2
2       B    NaN

Làm thế nào tốt nhất tôi có thể làm điều này? Vì một số lý do, các thuật ngữ liên quan đến điều này chỉ cho tôi thấy các liên kết về cách tạo các cột từ dicts và ngược lại: - /

Câu trả lời:


341

Bạn có thể sử dụng .replace. Ví dụ:

>>> df = pd.DataFrame({'col2': {0: 'a', 1: 2, 2: np.nan}, 'col1': {0: 'w', 1: 1, 2: 2}})
>>> di = {1: "A", 2: "B"}
>>> df
  col1 col2
0    w    a
1    1    2
2    2  NaN
>>> df.replace({"col1": di})
  col1 col2
0    w    a
1    A    2
2    B  NaN

hoặc trực tiếp trên Series, tức là df["col1"].replace(di, inplace=True).


1
Nó không hoạt động với tôi khi col```` is tuple. The error info is không thể so sánh các loại 'ndarray (dtype = object)' và 'tuple'```
Pengju Zhao

18
Dường như điều này không có tác dụng nữa ở tất cả , mà không có gì ngạc nhiên cho câu trả lời là từ 4 năm về trước. Câu hỏi này cần một câu trả lời mới được đưa ra về cách thức hoạt động nói chung ...
PrestonH

2
@PrestonH Nó hoạt động hoàn hảo cho tôi. Chạy:'3.6.1 |Anaconda custom (64-bit)| (default, May 11 2017, 13:25:24) [MSC v.1900 64 bit (AMD64)]'
Dan

Nó làm việc cho tôi. Nhưng làm thế nào nếu tôi muốn thay thế các giá trị trong TẤT CẢ các cột?
famargar

2
Phương pháp duy nhất phù hợp với tôi trong các câu trả lời được hiển thị là thay thế trực tiếp trên Sê-ri. Cảm ơn!
Dirigo

243

map có thể nhanh hơn nhiều replace

Nếu từ điển của bạn có nhiều hơn một vài khóa, việc sử dụng mapcó thể nhanh hơn nhiều replace. Có hai phiên bản của phương pháp này, tùy thuộc vào việc từ điển của bạn có ánh xạ toàn bộ tất cả các giá trị có thể hay không (và cả việc bạn muốn các kết quả không khớp để giữ các giá trị của chúng hay được chuyển đổi thành NaN):

Bản đồ toàn diện

Trong trường hợp này, hình thức rất đơn giản:

df['col1'].map(di)       # note: if the dictionary does not exhaustively map all
                         # entries then non-matched entries are changed to NaNs

Mặc dù maphầu hết thường lấy một hàm làm đối số của nó, nhưng nó có thể lấy từ điển hoặc sê-ri : Tài liệu cho Pandas.series.map

Bản đồ không toàn diện

Nếu bạn có một ánh xạ không đầy đủ và muốn giữ lại các biến hiện có cho các kết quả không khớp, bạn có thể thêm fillna:

df['col1'].map(di).fillna(df['col1'])

như trong câu trả lời của @ jpp tại đây: Thay thế các giá trị trong chuỗi gấu trúc thông qua từ điển một cách hiệu quả

Điểm chuẩn

Sử dụng dữ liệu sau với gấu trúc phiên bản 0.23.1:

di = {1: "A", 2: "B", 3: "C", 4: "D", 5: "E", 6: "F", 7: "G", 8: "H" }
df = pd.DataFrame({ 'col1': np.random.choice( range(1,9), 100000 ) })

và thử nghiệm với %timeit, nó dường như mapnhanh hơn khoảng 10 lần replace.

Lưu ý rằng việc tăng tốc của bạn mapsẽ thay đổi theo dữ liệu của bạn. Việc tăng tốc lớn nhất dường như là với các từ điển lớn và thay thế toàn diện. Xem câu trả lời @jpp (được liên kết ở trên) để biết thêm điểm chuẩn và thảo luận.


17
Khối mã cuối cùng cho câu trả lời này chắc chắn không phải là thanh lịch nhất, nhưng câu trả lời này xứng đáng với một số tín dụng. Đó là các đơn đặt hàng có cường độ nhanh hơn cho các từ điển lớn và không sử dụng hết RAM của tôi. Nó đã ánh xạ lại một tệp 10.000 dòng bằng một từ điển có khoảng 9 triệu mục trong nửa phút. Các df.replacechức năng, trong khi gọn gàng và hữu ích cho các dicts nhỏ, đã bị hỏng sau khi chạy trong 20 phút hoặc lâu hơn.
Griffinc


@griffinc Cảm ơn phản hồi và lưu ý rằng tôi đã cập nhật câu trả lời này bằng một cách đơn giản hơn nhiều để thực hiện trường hợp không toàn diện (cảm ơn @jpp)
JohnE

1
mapcũng hoạt động trên một chỉ mục nơi tôi không thể tìm ra cách để làm điều đó vớireplace
Max Ghenis 15/03/19

1
@AlexSB Tôi không thể đưa ra một câu trả lời hoàn toàn chung chung, nhưng tôi nghĩ rằng bản đồ sẽ nhanh hơn và hoàn thành (tôi nghĩ) điều tương tự. Nói chung, hợp nhất sẽ chậm hơn các tùy chọn khác làm điều tương tự.
JohnE

59

Có một chút mơ hồ trong câu hỏi của bạn. Có ít nhất ba hai cách hiểu:

  1. các khóa trong ditham chiếu đến các giá trị chỉ mục
  2. các khóa trong ditham chiếu đến df['col1']các giá trị
  3. các khóa trong ditham chiếu đến các vị trí chỉ mục (không phải câu hỏi của OP, nhưng được ném vào cho vui.)

Dưới đây là một giải pháp cho từng trường hợp.


Trường hợp 1: Nếu các khóa dicó nghĩa là tham chiếu đến các giá trị chỉ mục, thì bạn có thể sử dụng updatephương thức:

df['col1'].update(pd.Series(di))

Ví dụ,

import pandas as pd
import numpy as np

df = pd.DataFrame({'col1':['w', 10, 20],
                   'col2': ['a', 30, np.nan]},
                  index=[1,2,0])
#   col1 col2
# 1    w    a
# 2   10   30
# 0   20  NaN

di = {0: "A", 2: "B"}

# The value at the 0-index is mapped to 'A', the value at the 2-index is mapped to 'B'
df['col1'].update(pd.Series(di))
print(df)

sản lượng

  col1 col2
1    w    a
2    B   30
0    A  NaN

Tôi đã sửa đổi các giá trị từ bài viết gốc của bạn để rõ ràng hơn những gì updateđang làm. Lưu ý cách các khóa trong diđược liên kết với các giá trị chỉ mục. Thứ tự của các giá trị chỉ mục - nghĩa là các vị trí chỉ mục - không quan trọng.


Trường hợp 2: Nếu các khóa trong ditham chiếu đến df['col1']các giá trị, thì @DanAllan và @DSM chỉ ra cách đạt được điều này với replace:

import pandas as pd
import numpy as np

df = pd.DataFrame({'col1':['w', 10, 20],
                   'col2': ['a', 30, np.nan]},
                  index=[1,2,0])
print(df)
#   col1 col2
# 1    w    a
# 2   10   30
# 0   20  NaN

di = {10: "A", 20: "B"}

# The values 10 and 20 are replaced by 'A' and 'B'
df['col1'].replace(di, inplace=True)
print(df)

sản lượng

  col1 col2
1    w    a
2    A   30
0    B  NaN

Lưu ý làm thế nào trong trường hợp này, các khóa trong diđã được thay đổi để khớp với các giá trị trong df['col1'].


Trường hợp 3: Nếu các khóa trong ditham chiếu đến các vị trí chỉ mục, thì bạn có thể sử dụng

df['col1'].put(di.keys(), di.values())

từ

df = pd.DataFrame({'col1':['w', 10, 20],
                   'col2': ['a', 30, np.nan]},
                  index=[1,2,0])
di = {0: "A", 2: "B"}

# The values at the 0 and 2 index locations are replaced by 'A' and 'B'
df['col1'].put(di.keys(), di.values())
print(df)

sản lượng

  col1 col2
1    A    a
2   10   30
0    B  NaN

Ở đây, các hàng đầu tiên và thứ ba được thay đổi, bởi vì các phím trong di02, trong đó có chỉ mục 0 dựa trên Python tham khảo các địa điểm đầu tiên và thứ ba.


replacelà tốt như nhau, và có thể là một từ tốt hơn cho những gì đang xảy ra ở đây.
Dan Allan

Không phải khung dữ liệu đích được đăng của OP đã loại bỏ sự mơ hồ? Tuy nhiên, câu trả lời này là hữu ích, vì vậy +1.
DSM

@DSM: Rất tiếc, bạn đúng, không có khả năng Case3, nhưng tôi không nghĩ khung dữ liệu đích của OP phân biệt Case1 với Case2 vì các giá trị chỉ mục bằng với các giá trị cột.
unutbu

Giống như một số người khác được đăng, phương pháp của @ DSM không may làm việc với tôi, nhưng trường hợp 1 của @ unutbu đã làm việc. update()có vẻ hơi ít so với replace(), nhưng ít nhất nó hoạt động.
Geoff

4

Thêm vào câu hỏi này nếu bạn có nhiều hơn một cột để ánh xạ lại trong khung dữ liệu dữ liệu:

def remap(data,dict_labels):
    """
    This function take in a dictionnary of labels : dict_labels 
    and replace the values (previously labelencode) into the string.

    ex: dict_labels = {{'col1':{1:'A',2:'B'}}

    """
    for field,values in dict_labels.items():
        print("I am remapping %s"%field)
        data.replace({field:values},inplace=True)
    print("DONE")

    return data

Hy vọng nó có thể hữu ích cho ai đó.

Chúc mừng


1
Chức năng này đã được cung cấp bởi DataFrame.replace(), mặc dù tôi không biết khi nào nó được thêm vào.
AMC

3

DSM có câu trả lời được chấp nhận, nhưng mã hóa dường như không hoạt động với tất cả mọi người. Dưới đây là một phiên bản hoạt động với phiên bản gấu trúc hiện tại (0.23.4 tính đến 8/2018):

import pandas as pd

df = pd.DataFrame({'col1': [1, 2, 2, 3, 1],
            'col2': ['negative', 'positive', 'neutral', 'neutral', 'positive']})

conversion_dict = {'negative': -1, 'neutral': 0, 'positive': 1}
df['converted_column'] = df['col2'].replace(conversion_dict)

print(df.head())

Bạn sẽ thấy nó trông giống như:

   col1      col2  converted_column
0     1  negative                -1
1     2  positive                 1
2     2   neutral                 0
3     3   neutral                 0
4     1  positive                 1

Các tài liệu cho pandas.DataFrame.replace đang ở đây .


Tôi chưa bao giờ gặp vấn đề khi nhận được câu trả lời của DSM và tôi đoán với tổng số phiếu cao, hầu hết những người khác cũng không biết. Bạn có thể muốn được cụ thể hơn về vấn đề bạn đang gặp phải. Có lẽ nó liên quan đến dữ liệu mẫu của bạn khác với dữ liệu của DSM?
JohnE

Hmm, có lẽ là một vấn đề phiên bản. Tuy nhiên, cả hai câu trả lời đều ở đây.
wordsforthewise

1
Giải pháp trong câu trả lời được chấp nhận chỉ hoạt động trên một số loại nhất định, Series.map()có vẻ linh hoạt hơn.
AMC

2

Hoặc làm apply:

df['col1'].apply(lambda x: {1: "A", 2: "B"}.get(x,x))

Bản giới thiệu:

>>> df['col1']=df['col1'].apply(lambda x: {1: "A", 2: "B"}.get(x,x))
>>> df
  col1 col2
0    w    a
1    1    2
2    2  NaN
>>> 

Điều gì xảy ra khi didict của bạn là một danh sách của danh sách? Làm thế nào bạn có thể ánh xạ chỉ một giá trị trong danh sách?
FaCoffee

Bạn có thể, mặc dù tôi không thấy lý do tại sao bạn muốn.
AMC

2

Đưa ra maplà nhanh hơn thay thế (giải pháp của @ JohnE), bạn cần cẩn thận với ánh xạ không cạn kiệt nơi bạn định ánh xạ các giá trị cụ thể tớiNaN . Phương thức thích hợp trong trường hợp này yêu cầu bạn maskSê-ri khi bạn .fillna, nếu không, bạn hoàn tác ánh xạ tới NaN.

import pandas as pd
import numpy as np

d = {'m': 'Male', 'f': 'Female', 'missing': np.NaN}
df = pd.DataFrame({'gender': ['m', 'f', 'missing', 'Male', 'U']})

keep_nan = [k for k,v in d.items() if pd.isnull(v)]
s = df['gender']

df['mapped'] = s.map(d).fillna(s.mask(s.isin(keep_nan)))

    gender  mapped
0        m    Male
1        f  Female
2  missing     NaN
3     Male    Male
4        U       U

1

Một giải pháp hoàn chỉnh tốt đẹp giữ bản đồ nhãn lớp của bạn:

labels = features['col1'].unique()
labels_dict = dict(zip(labels, range(len(labels))))
features = features.replace({"col1": labels_dict})

Bằng cách này, bất cứ lúc nào bạn cũng có thể tham khảo nhãn lớp gốc từ nhãn_dict.


1

Là một phần mở rộng cho những gì đã được đề xuất bởi Nico Coallier (áp dụng cho nhiều cột) và U10-Forward (sử dụng kiểu phương thức áp dụng) và tóm tắt nó thành một lớp lót mà tôi đề xuất:

df.loc[:,['col1','col2']].transform(lambda x: x.map(lambda x: {1: "A", 2: "B"}.get(x,x))

Các .transform()quy trình mỗi cột như một chuỗi. Trái ngược với việc .apply()vượt qua các cột được tổng hợp trong DataFrame.

Do đó, bạn có thể áp dụng phương pháp Sê-ri map().

Cuối cùng, và tôi đã phát hiện ra hành vi này nhờ vào U10, bạn có thể sử dụng toàn bộ Sê-ri trong biểu thức .get (). Trừ khi tôi đã hiểu sai hành vi của nó và nó xử lý tuần tự chuỗi thay vì bitw chính xác.
Các .get(x,x)tài khoản cho các giá trị bạn đã không đề cập trong từ điển bản đồ của bạn đó sẽ được coi là Nan khác bởi các .map()phương pháp


Các .transform()quy trình mỗi cột như một chuỗi. Trái ngược với việc .apply()vượt qua các cột được tổng hợp trong DataFrame. Tôi chỉ cần cố gắng, apply()hoạt động tốt. Không cần sử dụng loc, điều này có vẻ quá phức tạp. df[["col1", "col2"]].apply(lambda col: col.map(lambda elem: my_dict.get(elem, elem)))nên làm việc tốt Các .get(x,x)tài khoản cho các giá trị bạn không đề cập trong từ điển ánh xạ của bạn sẽ được coi là Nan nếu không theo .map()phương pháp Bạn cũng có thể sử dụng fillna()sau đó.
AMC

Cuối cùng, và tôi đã phát hiện ra hành vi này nhờ vào U10, bạn có thể sử dụng toàn bộ Sê-ri trong biểu thức .get (). Trừ khi tôi đã hiểu sai hành vi của nó và nó xử lý tuần tự chuỗi thay vì bitw chính xác. Tôi không thể tái tạo điều này, bạn có thể giải thích? Các biến được đặt tên giống hệt nhau có khả năng đóng một số vai trò ở đây.
AMC

0

Một cách tiếp cận gấu trúc bản địa hơn là áp dụng chức năng thay thế như dưới đây:

def multiple_replace(dict, text):
  # Create a regular expression  from the dictionary keys
  regex = re.compile("(%s)" % "|".join(map(re.escape, dict.keys())))

  # For each match, look-up corresponding value in dictionary
  return regex.sub(lambda mo: dict[mo.string[mo.start():mo.end()]], text) 

Khi bạn đã xác định hàm, bạn có thể áp dụng nó cho khung dữ liệu của mình.

di = {1: "A", 2: "B"}
df['col1'] = df.apply(lambda row: multiple_replace(di, row['col1']), axis=1)

Một cách tiếp cận gấu trúc bản địa hơn là áp dụng một chức năng thay thế như dưới đây Làm thế nào mà "bản địa" (thành ngữ?) Hơn các phương pháp đơn giản hơn nhiều do Pandas cung cấp?
AMC
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.