ฟอร์ลูปในแพนด้าแย่จริงหรือ? ควรดูแลเมื่อใด


111

มีforลูปมัน "เลวร้าย"? ถ้าไม่เช่นนั้นในสถานการณ์ใดจะดีไปกว่าการใช้แนวทาง "vectorized" แบบเดิม ๆ 1

ฉันคุ้นเคยกับแนวคิดของ "vectorization" และวิธีที่แพนด้าใช้เทคนิคเวกเตอร์เพื่อเร่งความเร็วในการคำนวณ ฟังก์ชัน Vectorized จะออกอากาศการดำเนินการในทั้งซีรีส์หรือ DataFrame เพื่อให้ได้ความเร็วที่สูงกว่าการทำซ้ำตามปกติในข้อมูล

อย่างไรก็ตามฉันค่อนข้างแปลกใจที่เห็นโค้ดจำนวนมาก (รวมถึงจากคำตอบใน Stack Overflow) เสนอวิธีแก้ปัญหาที่เกี่ยวข้องกับการวนซ้ำข้อมูลโดยใช้การวนซ้ำforและความเข้าใจในรายการ เอกสารประกอบและ API บอกว่าลูป "ไม่ดี" และควร "ไม่" ซ้ำในอาร์เรย์ชุดข้อมูลหรือเฟรมข้อมูล แล้วทำไมบางครั้งฉันจึงเห็นผู้ใช้แนะนำโซลูชันแบบวนซ้ำ


1 - แม้ว่าคำถามจะฟังดูกว้าง แต่ความจริงก็คือมีสถานการณ์ที่เฉพาะเจาะจงมากเมื่อforลูปมักจะดีกว่าการวนซ้ำตามอัตภาพ โพสต์นี้มีจุดมุ่งหมายเพื่อจับภาพนี้สำหรับลูกหลาน

คำตอบ:


154

TLDR; ไม่forลูปไม่ใช่ผ้าห่ม "ไม่ดี" อย่างน้อยก็ไม่เสมอไป อาจจะแม่นยำกว่าที่จะบอกว่าการดำเนินการแบบเวกเตอร์บางอย่างช้ากว่าการทำซ้ำเมื่อเทียบกับการบอกว่าการวนซ้ำเร็วกว่าการดำเนินการแบบเวกเตอร์บางอย่าง การรู้ว่าเมื่อไรและเหตุใดจึงเป็นกุญแจสำคัญในการทำให้โค้ดของคุณมีประสิทธิภาพสูงสุด โดยสรุปนี่คือสถานการณ์ที่ควรค่าแก่การพิจารณาทางเลือกอื่นสำหรับฟังก์ชันแพนด้าแบบเวกเตอร์:

  1. เมื่อข้อมูลของคุณมีขนาดเล็ก (... ขึ้นอยู่กับสิ่งที่คุณกำลังทำอยู่)
  2. เมื่อจัดการกับobject/ ผสม dtypes
  3. เมื่อใช้ฟังก์ชันการเข้าถึงstr/ regex

ลองตรวจสอบสถานการณ์เหล่านี้ทีละรายการ


การทำซ้ำ v / s Vectorization บนข้อมูลขนาดเล็ก

Pandas ปฏิบัติตามแนวทาง"Convention Over Configuration"ในการออกแบบ API ซึ่งหมายความว่า API เดียวกันได้รับการติดตั้งเพื่อรองรับข้อมูลและกรณีการใช้งานที่หลากหลาย

เมื่อมีการเรียกใช้ฟังก์ชันแพนด้าฟังก์ชันต่อไปนี้จะต้องได้รับการจัดการภายในเพื่อให้แน่ใจว่าทำงานได้

  1. การจัดแนวดัชนี / แกน
  2. การจัดการประเภทข้อมูลผสม
  3. การจัดการข้อมูลที่ขาดหายไป

ฟังก์ชั่นเกือบทุกคนจะต้องจัดการกับเหล่านี้เพื่อขอบเขตที่แตกต่างกันและนำเสนอนี้ค่าใช้จ่าย ค่าโสหุ้ยมีค่าน้อยกว่าสำหรับฟังก์ชันตัวเลข (เช่นSeries.add) ในขณะที่ฟังก์ชันสตริงจะเด่นชัดกว่า (เช่นSeries.str.replace)

forในทางกลับกันลูปเร็วกว่าที่คุณคิด สิ่งที่ดีไปกว่านั้นคือการทำความเข้าใจรายการ (ซึ่งสร้างรายการผ่านforลูป) จะเร็วกว่าเนื่องจากมีการปรับกลไกการทำซ้ำสำหรับการสร้างรายการ

ความเข้าใจในรายการเป็นไปตามรูปแบบ

[f(x) for x in seq]

seqชุดแพนด้าหรือคอลัมน์ DataFrame อยู่ที่ไหน หรือเมื่อทำงานในหลายคอลัมน์

[f(x, y) for x, y in zip(seq1, seq2)]

คอลัมน์ที่ไหนseq1และอยู่ที่ไหนseq2

การเปรียบเทียบตัวเลข
พิจารณาการดำเนินการจัดทำดัชนีบูลีนอย่างง่าย วิธีรายการความเข้าใจที่ได้รับการหมดเวลากับSeries.ne( !=) queryและ นี่คือฟังก์ชั่น:

# Boolean indexing with Numeric value comparison.
df[df.A != df.B]                            # vectorized !=
df.query('A != B')                          # query (numexpr)
df[[x != y for x, y in zip(df.A, df.B)]]    # list comp

เพื่อความง่ายฉันได้ใช้perfplotแพ็คเกจเพื่อเรียกใช้การทดสอบตลอดเวลาในโพสต์นี้ กำหนดเวลาสำหรับการดำเนินการข้างต้นอยู่ด้านล่าง:

ป้อนคำอธิบายภาพที่นี่

ความเข้าใจในรายการมีประสิทธิภาพสูงกว่าqueryN ที่มีขนาดปานกลางและยังมีประสิทธิภาพดีกว่าการเปรียบเทียบเวกเตอร์ที่ไม่เท่ากับการเปรียบเทียบสำหรับ N ขนาดเล็ก แต่น่าเสียดายที่ความเข้าใจในรายการจะปรับขนาดเป็นเชิงเส้นดังนั้นจึงไม่ได้ให้ประสิทธิภาพที่เพิ่มขึ้นมากสำหรับ N ที่ใหญ่กว่า

หมายเหตุ
เป็นสิ่งที่ควรค่าแก่การกล่าวถึงประโยชน์ส่วนใหญ่ของการทำความเข้าใจรายการมาจากการที่คุณไม่ต้องกังวลเกี่ยวกับการจัดแนวดัชนี แต่หมายความว่าหากรหัสของคุณขึ้นอยู่กับการจัดแนวดัชนีสิ่งนี้จะเสียหาย ในบางกรณีการดำเนินการ vectorised บนอาร์เรย์ NumPy ที่อยู่ข้างใต้ถือได้ว่าเป็นการนำ "สิ่งที่ดีที่สุดของทั้งสองโลก" มาใช้ทำให้สามารถสร้างเวกเตอร์ได้โดยไม่ต้องมีค่าใช้จ่ายที่ไม่จำเป็นทั้งหมดของฟังก์ชันแพนด้า ซึ่งหมายความว่าคุณสามารถเขียนการดำเนินการด้านบนใหม่เป็น

df[df.A.values != df.B.values]

ซึ่งมีประสิทธิภาพดีกว่าทั้งแพนด้าและรายการที่เทียบเท่า:

การทำให้เป็นเวกเตอร์ NumPy อยู่นอกขอบเขตของโพสต์นี้ แต่ควรพิจารณาอย่างแน่นอนหากประสิทธิภาพมีความสำคัญ

มูลค่านับ
การอีกตัวอย่างหนึ่ง - ในขณะนี้อีกด้วยสร้างหลามวานิลลาที่เป็นได้เร็วขึ้นกว่าสำหรับวง collections.Counter- ข้อกำหนดทั่วไปคือการคำนวณการนับค่าและส่งคืนผลลัพธ์เป็นพจนานุกรม นี้จะกระทำด้วยvalue_counts, np.uniqueและCounter:

# Value Counts comparison.
ser.value_counts(sort=False).to_dict()           # value_counts
dict(zip(*np.unique(ser, return_counts=True)))   # np.unique
Counter(ser)                                     # Counter

ป้อนคำอธิบายภาพที่นี่

ผลลัพธ์จะเด่นชัดมากขึ้นCounterชนะทั้งสองวิธีแบบเวกเตอร์สำหรับช่วงขนาดเล็กที่ใหญ่กว่า N (~ 3500)

หมายเหตุ
เรื่องไม่สำคัญเพิ่มเติม (มารยาท @ user2357112) Counterจะดำเนินการกับคันเร่ง Cดังนั้นในขณะที่มันยังคงมีการทำงานร่วมกับงูหลามวัตถุแทนของประเภทข้อมูล C พื้นฐานก็ยังคงเป็นเร็วกว่าforห่วง พลังงูเหลือม!

แน่นอนว่าสิ่งที่ต้องทำก็คือประสิทธิภาพขึ้นอยู่กับข้อมูลและกรณีการใช้งานของคุณ ประเด็นของตัวอย่างเหล่านี้คือการโน้มน้าวให้คุณไม่คิดว่าโซลูชันเหล่านี้เป็นตัวเลือกที่ถูกต้อง หากสิ่งเหล่านี้ยังไม่ให้ประสิทธิภาพที่คุณต้องการก็มีcythonและnumbaอยู่เสมอ ลองเพิ่มการทดสอบนี้ลงในส่วนผสม

from numba import njit, prange

@njit(parallel=True)
def get_mask(x, y):
    result = [False] * len(x)
    for i in prange(len(x)):
        result[i] = x[i] != y[i]

    return np.array(result)

df[get_mask(df.A.values, df.B.values)] # numba

ป้อนคำอธิบายภาพที่นี่

Numba นำเสนอการรวบรวม JIT ของรหัสหลามแบบวนซ้ำเป็นรหัสเวกเตอร์ที่ทรงพลังมาก การทำความเข้าใจวิธีการทำงานของ numba เกี่ยวข้องกับช่วงการเรียนรู้


การทำงานกับ Mixed / objectdtypes

การเปรียบเทียบแบบใช้สตริงการ
ทบทวนตัวอย่างการกรองจากส่วนแรกจะเกิดอะไรขึ้นถ้าคอลัมน์ที่กำลังเปรียบเทียบเป็นสตริง พิจารณา 3 ฟังก์ชั่นเดียวกันข้างต้น แต่ด้วย DataFrame ที่ป้อนข้อมูลเป็นสตริง

# Boolean indexing with string value comparison.
df[df.A != df.B]                            # vectorized !=
df.query('A != B')                          # query (numexpr)
df[[x != y for x, y in zip(df.A, df.B)]]    # list comp

ป้อนคำอธิบายภาพที่นี่

มีอะไรเปลี่ยนแปลงบ้าง? สิ่งที่ควรทราบก็คือการดำเนินการของสตริงนั้นยากที่จะทำให้เป็นเวกเตอร์ได้ หมีแพนด้าถือว่าสตริงเป็นวัตถุและการดำเนินการทั้งหมดกับอ็อบเจกต์กลับเป็นการใช้งานแบบวนซ้ำที่ช้า

ตอนนี้เนื่องจากการใช้งานแบบวนรอบนี้ล้อมรอบด้วยค่าโสหุ้ยทั้งหมดที่กล่าวมาข้างต้นจึงมีความแตกต่างของขนาดคงที่ระหว่างโซลูชันเหล่านี้แม้ว่าจะมีขนาดเท่ากันก็ตาม

เมื่อพูดถึงการดำเนินการกับวัตถุที่เปลี่ยนแปลงไม่ได้ / ซับซ้อนจะไม่มีการเปรียบเทียบ ความเข้าใจในรายการมีประสิทธิภาพสูงกว่าการดำเนินการทั้งหมดที่เกี่ยวข้องกับคำสั่งและรายการ

การเข้าถึงค่าพจนานุกรมโดยคีย์
นี่คือการกำหนดเวลาสำหรับการดำเนินการสองรายการที่ดึงค่าจากคอลัมน์ของพจนานุกรม: mapและความเข้าใจรายการ การตั้งค่าอยู่ในภาคผนวกภายใต้หัวข้อ "ข้อมูลโค้ด"

# Dictionary value extraction.
ser.map(operator.itemgetter('value'))     # map
pd.Series([x.get('value') for x in ser])  # list comprehension

ป้อนคำอธิบายภาพที่นี่

ตำแหน่งรายการการจัดทำดัชนี
การจับเวลา 3 การดำเนินงานที่แยกองค์ประกอบ 0 จากรายการของคอลัมน์ (การจัดการข้อยกเว้น) ซึ่งเป็นmap, str.getวิธีการเข้าถึงและเข้าใจรายการ:

# List positional indexing. 
def get_0th(lst):
    try:
        return lst[0]
    # Handle empty lists and NaNs gracefully.
    except (IndexError, TypeError):
        return np.nan

ser.map(get_0th)                                          # map
ser.str[0]                                                # str accessor
pd.Series([x[0] if len(x) > 0 else np.nan for x in ser])  # list comp
pd.Series([get_0th(x) for x in ser])                      # list comp safe

หมายเหตุ
หากดัชนีมีความสำคัญคุณต้องทำ:

pd.Series([...], index=ser.index)

เมื่อสร้างชุดใหม่

ป้อนคำอธิบายภาพที่นี่

List Flattening
ตัวอย่างสุดท้ายคือการแบนรายการ นี่เป็นอีกหนึ่งปัญหาที่พบบ่อยและแสดงให้เห็นว่าไพ ธ อนบริสุทธิ์มีประสิทธิภาพเพียงใด

# Nested list flattening.
pd.DataFrame(ser.tolist()).stack().reset_index(drop=True)  # stack
pd.Series(list(chain.from_iterable(ser.tolist())))         # itertools.chain
pd.Series([y for x in ser for y in x])                     # nested list comp

ป้อนคำอธิบายภาพที่นี่

itertools.chain.from_iterableความเข้าใจในรายการที่ซ้อนกันทั้งคู่เป็นโครงสร้าง python ที่บริสุทธิ์และปรับขนาดได้ดีกว่าstackโซลูชันมาก

การกำหนดเวลาเหล่านี้เป็นข้อบ่งชี้ที่ชัดเจนถึงข้อเท็จจริงที่ว่าแพนด้าไม่ได้มีอุปกรณ์ที่จะทำงานร่วมกับ dtypes แบบผสมและคุณควรละเว้นจากการใช้เพื่อทำเช่นนั้น หากเป็นไปได้ข้อมูลควรแสดงเป็นค่าสเกลาร์ (ints / floats / strings) ในคอลัมน์แยกกัน

ประการสุดท้ายการบังคับใช้โซลูชันเหล่านี้ขึ้นอยู่กับข้อมูลของคุณอย่างกว้างขวาง ดังนั้นสิ่งที่ดีที่สุดที่ควรทำคือทดสอบการดำเนินการเหล่านี้กับข้อมูลของคุณก่อนตัดสินใจว่าจะใช้อะไร สังเกตว่าฉันไม่ได้จับเวลาapplyในการแก้ปัญหาเหล่านี้เพราะมันจะทำให้กราฟบิดเบี้ยว (ใช่มันช้ามาก)


Regex Operations และ.strAccessor Methods

นุ่นสามารถนำไปใช้ดำเนินงาน regex เช่นstr.contains, str.extractและstr.extractallเช่นเดียวกับคนอื่น ๆ "vectorized" การดำเนินงานสตริง (เช่นstr.split, str.find ,str.translate`, และอื่น ๆ ) ในคอลัมน์สตริง ฟังก์ชั่นเหล่านี้ช้ากว่าการทำความเข้าใจรายการและหมายถึงฟังก์ชันอำนวยความสะดวกมากกว่าสิ่งอื่นใด

โดยปกติแล้วการคอมไพล์รูปแบบ regex ล่วงหน้าจะเร็วกว่ามากและทำซ้ำบนข้อมูลของคุณด้วยre.compile(ดูที่การใช้ re.compile ของ Python คุ้มค่าหรือไม่ ) รายการเทียบเท่าคอมพ์str.containsมีลักษณะดังนี้:

p = re.compile(...)
ser2 = pd.Series([x for x in ser if p.search(x)])

หรือ,

ser2 = ser[[bool(p.search(x)) for x in ser]]

หากคุณต้องการจัดการ NaN คุณสามารถทำสิ่งต่างๆเช่น

ser[[bool(p.search(x)) if pd.notnull(x) else False for x in ser]]

รายการที่เทียบเท่ากับstr.extract(ไม่มีกลุ่ม) จะมีลักษณะดังนี้:

df['col2'] = [p.search(x).group(0) for x in df['col']]

หากคุณต้องการจัดการกับการไม่ตรงกันและ NaN คุณสามารถใช้ฟังก์ชันที่กำหนดเองได้ (ยังเร็วกว่า!):

def matcher(x):
    m = p.search(str(x))
    if m:
        return m.group(0)
    return np.nan

df['col2'] = [matcher(x) for x in df['col']]

matcherฟังก์ชั่นจะขยายมาก สามารถติดตั้งเพื่อส่งคืนรายการสำหรับแต่ละกลุ่มการจับภาพได้ตามต้องการ เพียงแค่แยกการสืบค้นgroupหรือgroupsแอตทริบิวต์ของวัตถุที่ตรงกัน

สำหรับstr.extractallเปลี่ยนp.searchเป็นp.findall.

การแยกสตริง
พิจารณาการกรองแบบง่าย แนวคิดคือการแยกตัวเลข 4 หลักหากนำหน้าด้วยตัวอักษรตัวพิมพ์ใหญ่

# Extracting strings.
p = re.compile(r'(?<=[A-Z])(\d{4})')
def matcher(x):
    m = p.search(x)
    if m:
        return m.group(0)
    return np.nan

ser.str.extract(r'(?<=[A-Z])(\d{4})', expand=False)   #  str.extract
pd.Series([matcher(x) for x in ser])                  #  list comprehension

ป้อนคำอธิบายภาพที่นี่

ตัวอย่างเพิ่มเติม
การเปิดเผยข้อมูลทั้งหมด - ฉันเป็นผู้เขียน (บางส่วนหรือทั้งหมด) ของโพสต์เหล่านี้ตามรายการด้านล่าง


สรุป

ดังที่แสดงจากตัวอย่างด้านบนการวนซ้ำจะส่องสว่างเมื่อทำงานกับแถวเล็ก ๆ ของ DataFrames ประเภทข้อมูลผสมและนิพจน์ทั่วไป

ความเร็วที่คุณได้รับขึ้นอยู่กับข้อมูลและปัญหาของคุณดังนั้นระยะทางของคุณอาจแตกต่างกันไป สิ่งที่ดีที่สุดที่ต้องทำคือเรียกใช้การทดสอบอย่างรอบคอบและดูว่าการจ่ายเงินนั้นคุ้มค่ากับความพยายามหรือไม่

ฟังก์ชัน "vectorized" เปล่งประกายในความเรียบง่ายและความสามารถในการอ่านดังนั้นหากประสิทธิภาพไม่สำคัญคุณควรเลือกฟังก์ชั่นเหล่านี้อย่างแน่นอน

หมายเหตุด้านอื่นการดำเนินการสตริงบางอย่างเกี่ยวข้องกับข้อ จำกัด ที่สนับสนุนการใช้ NumPy ต่อไปนี้เป็นตัวอย่างสองตัวอย่างที่ทำให้เวกเตอร์ NumPy มีประสิทธิภาพดีกว่า python:

นอกจากนี้บางครั้งการทำงานบนอาร์เรย์พื้นฐานผ่านทาง.valuesตรงข้ามกับซีรี่ส์หรือ DataFrames สามารถให้ความเร็วที่ดีพอสำหรับสถานการณ์ปกติส่วนใหญ่ (ดูหมายเหตุในส่วนการเปรียบเทียบตัวเลขด้านบน) ดังนั้นสำหรับตัวอย่างเช่นจะแสดงช่วยเพิ่มประสิทธิภาพการทำงานทันทีผ่านทางdf[df.A.values != df.B.values] df[df.A != df.B]การใช้.valuesอาจไม่เหมาะสมในทุกสถานการณ์ แต่เป็นประโยชน์ที่ควรทราบ

ดังที่กล่าวไว้ข้างต้นขึ้นอยู่กับคุณที่จะตัดสินใจว่าโซลูชันเหล่านี้คุ้มค่ากับปัญหาในการนำไปใช้หรือไม่


ภาคผนวก: ข้อมูลโค้ด

import perfplot  
import operator 
import pandas as pd
import numpy as np
import re

from collections import Counter
from itertools import chain

# Boolean indexing with Numeric value comparison.
perfplot.show(
    setup=lambda n: pd.DataFrame(np.random.choice(1000, (n, 2)), columns=['A','B']),
    kernels=[
        lambda df: df[df.A != df.B],
        lambda df: df.query('A != B'),
        lambda df: df[[x != y for x, y in zip(df.A, df.B)]],
        lambda df: df[get_mask(df.A.values, df.B.values)]
    ],
    labels=['vectorized !=', 'query (numexpr)', 'list comp', 'numba'],
    n_range=[2**k for k in range(0, 15)],
    xlabel='N'
)

# Value Counts comparison.
perfplot.show(
    setup=lambda n: pd.Series(np.random.choice(1000, n)),
    kernels=[
        lambda ser: ser.value_counts(sort=False).to_dict(),
        lambda ser: dict(zip(*np.unique(ser, return_counts=True))),
        lambda ser: Counter(ser),
    ],
    labels=['value_counts', 'np.unique', 'Counter'],
    n_range=[2**k for k in range(0, 15)],
    xlabel='N',
    equality_check=lambda x, y: dict(x) == dict(y)
)

# Boolean indexing with string value comparison.
perfplot.show(
    setup=lambda n: pd.DataFrame(np.random.choice(1000, (n, 2)), columns=['A','B'], dtype=str),
    kernels=[
        lambda df: df[df.A != df.B],
        lambda df: df.query('A != B'),
        lambda df: df[[x != y for x, y in zip(df.A, df.B)]],
    ],
    labels=['vectorized !=', 'query (numexpr)', 'list comp'],
    n_range=[2**k for k in range(0, 15)],
    xlabel='N',
    equality_check=None
)

# Dictionary value extraction.
ser1 = pd.Series([{'key': 'abc', 'value': 123}, {'key': 'xyz', 'value': 456}])
perfplot.show(
    setup=lambda n: pd.concat([ser1] * n, ignore_index=True),
    kernels=[
        lambda ser: ser.map(operator.itemgetter('value')),
        lambda ser: pd.Series([x.get('value') for x in ser]),
    ],
    labels=['map', 'list comprehension'],
    n_range=[2**k for k in range(0, 15)],
    xlabel='N',
    equality_check=None
)

# List positional indexing. 
ser2 = pd.Series([['a', 'b', 'c'], [1, 2], []])        
perfplot.show(
    setup=lambda n: pd.concat([ser2] * n, ignore_index=True),
    kernels=[
        lambda ser: ser.map(get_0th),
        lambda ser: ser.str[0],
        lambda ser: pd.Series([x[0] if len(x) > 0 else np.nan for x in ser]),
        lambda ser: pd.Series([get_0th(x) for x in ser]),
    ],
    labels=['map', 'str accessor', 'list comprehension', 'list comp safe'],
    n_range=[2**k for k in range(0, 15)],
    xlabel='N',
    equality_check=None
)

# Nested list flattening.
ser3 = pd.Series([['a', 'b', 'c'], ['d', 'e'], ['f', 'g']])
perfplot.show(
    setup=lambda n: pd.concat([ser2] * n, ignore_index=True),
    kernels=[
        lambda ser: pd.DataFrame(ser.tolist()).stack().reset_index(drop=True),
        lambda ser: pd.Series(list(chain.from_iterable(ser.tolist()))),
        lambda ser: pd.Series([y for x in ser for y in x]),
    ],
    labels=['stack', 'itertools.chain', 'nested list comp'],
    n_range=[2**k for k in range(0, 15)],
    xlabel='N',    
    equality_check=None

)

# Extracting strings.
ser4 = pd.Series(['foo xyz', 'test A1234', 'D3345 xtz'])
perfplot.show(
    setup=lambda n: pd.concat([ser4] * n, ignore_index=True),
    kernels=[
        lambda ser: ser.str.extract(r'(?<=[A-Z])(\d{4})', expand=False),
        lambda ser: pd.Series([matcher(x) for x in ser])
    ],
    labels=['str.extract', 'list comprehension'],
    n_range=[2**k for k in range(0, 15)],
    xlabel='N',
    equality_check=None
)

pd.Seriesและpd.DataFrameตอนนี้รองรับการสร้างจาก iterables นั่นหมายความว่าเราสามารถส่ง Python generator ไปยังฟังก์ชัน constructor ได้แทนที่จะต้องสร้างรายการขึ้นมาก่อน (โดยใช้ list comp understandions) ซึ่งอาจช้ากว่าในหลาย ๆ กรณี อย่างไรก็ตามไม่สามารถกำหนดขนาดของเอาต์พุตของเครื่องกำเนิดไฟฟ้าได้ล่วงหน้า ฉันไม่แน่ใจว่าจะต้องเสียเวลา / หน่วยความจำมากแค่ไหน
GZ0

1
@ GZ0 IIRC การยอมรับตัวทำซ้ำเป็นส่วนเสริมล่าสุดของ API สำหรับ "นั่นหมายความว่าเราสามารถส่ง Python generator ไปยังฟังก์ชัน constructor ได้แทนที่จะต้องสร้าง list ขึ้นมาก่อน" ฉันไม่เห็นด้วย หน่วยความจำอาจไม่มีประสิทธิภาพ จากประสบการณ์ของฉันมักจะเร็วกว่าในการสร้างรายการและส่งต่อไป รายการประกอบด้วย FTW
cs95

@ cs95 ทำไมคุณไม่คิดว่าเครื่องปั่นไฟสามารถนำไปสู่การเพิ่มประสิทธิภาพได้? หรือคุณได้ทำการทดสอบเกี่ยวกับเรื่องนี้หรือไม่?
GZ0

@ GZ0 ฉันไม่ได้บอกว่าเครื่องกำเนิดไฟฟ้าไม่ได้นำไปสู่การเพิ่มประสิทธิภาพ แต่ฉันกำลังบอกว่าคุณไม่ได้รับประโยชน์มากเท่ากับที่คุณใช้ความเข้าใจในรายการ มีค่าโสหุ้ยที่เกี่ยวข้องกับการเริ่มต้น genexps การรักษาสถานะ ฯลฯ ซึ่งรายการคอมพ์ไม่มี มีตัวอย่างมากมายที่นี่พร้อมความเข้าใจในรายการที่คุณสามารถใช้เวลาเทียบกับความเข้าใจของเครื่องกำเนิดไฟฟ้าเพื่อเปรียบเทียบได้ นี่คือตัวอย่างที่คุณสามารถเรียกใช้ได้ในขณะนี้: ser = pd.Series([['a', 'b', 'c'], ['d', 'e'], ['f', 'g']] * 10000); %timeit pd.Series(y for x in ser for y in x); %timeit pd.Series([y for x in ser for y in x])
cs95

1
@ cs95 หมายเหตุว่าสำหรับ iterables รู้จักกันขนาดก็จะเร็วขึ้นเพื่อสร้างpd.Seriesจากพวกเขาโดยตรงมากกว่าการแปลงให้รายการเช่นpd.Series(range(10000)), pd.Series("a" * 10000)และpd.Series(pd.Index(range(10000)))จะเร็วกว่าคู่ของรายการของพวกเขา (คนสุดท้ายคือแม้เพียงเล็กน้อยเร็วกว่าpd.Index.to_series.
GZ0

1

ในระยะสั้น

  • สำหรับ loop + iterrowsนั้นช้ามาก ค่าโสหุ้ยไม่สำคัญในแถว ~ 1k แต่สังเกตได้ในแถว 10k +
  • สำหรับ loop + itertuplesนั้นเร็วกว่าiterrowsหรือapplyมาก
  • vectorization มักจะเร็วกว่า itertuples

เกณฑ์มาตรฐาน ป้อนคำอธิบายภาพที่นี่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.