Python: деректен өз есебіңізге дейін 24-сабақ (барлығы 56)
Тізім бар болса, pandas не үшін керек: өлшем не көрсетті
Python курсының жиырма төртінші сабағы. pandas модулі басталуға тиіс сұрақтан басталады: ол соған тұра ма. Жауапты сенім емес, өлшем береді. pandas қырық есе жылдам болатын жер, тек екі есе болатын жер, кәдімгі циклден баяу болатын жер, жады неге тұрады — және кесте қашан керек екені.
Не үшін керек
Жиынтық деректерді желіден алуды, оларды дерекқорға салуды және сұраныспен жауап беруді үйренді. Одан әрі бір рет қойылмайтын сұрақтар басталады: әр санат бойынша қанша, ол ай сайын қалай өзгерді, басқа валютаға айналдырсақ не болады, енді сол сұрақтың демалыссыз нұсқасы. Тізімде мұндай сұрақтың әрқайсысы — жаңа цикл және жаңа сөздік, ал он сұрақтан кейін бағдарлама солардан ғана тұрады.
pandas туралы «ол жылдам» деп айту әдетке айналған. Бұл шындықтың жартысы, ал екінші жартысы пайдалырақ. Сондықтан модуль уәдеден емес, секундомерден басталады: бір сұрақ, екі тәсілмен қойылған.
Бірден тұтас
Алдымен орнату — бүкіл модульге бір рет. Жұмыс ортасын екінші сабақта құрғанбыз, кітапхана соған орнатылады:
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install pandas==3.0.5
Нұсқа әдейі бекітілген. pandas кестені өзінше басып шығарады, ал басқа нұсқада сабақтың шығысы сіздікінен өзгеше болуы мүмкін — қате жібергеніңізден емес.
zamer.py файлы. Ол бір сұрақты — санат бойынша орташа чекті — алдымен қолмен, содан кейін pandas арқылы қойып, жауаптарды салыстырады.
"""24-сабақ: тізім бар болса, pandas не үшін керек.
Бір сұрақ — санат бойынша орташа чек — екі рет қойылған: тізіммен қолмен және
pandas арқылы. Алдымен жауаптар сәйкес келуге тиіс, содан кейін ғана әрқайсысы
неге тұрады деп таласуға болады.
"""
import pandas as pd
KINDS = ["тамақ", "жанармай", "пәтерақы", "байланыс", "басқа"]
BASE = {"тамақ": 2500, "жанармай": 9000, "пәтерақы": 60000, "байланыс": 4000, "басқа": 1500}
N = 300_000
# Кесте санауышпен құрылған, кездейсоқ сансыз: бәрінде бірдей болады.
rows = [(KINDS[i % 5], BASE[KINDS[i % 5]] + (i * 37) % 900 - 450) for i in range(N)]
print("жол саны:", len(rows))
print()
print("== қолмен: екі сөздік және бір өту")
total, count = {}, {}
for kind, amount in rows:
total[kind] = total.get(kind, 0) + amount
count[kind] = count.get(kind, 0) + 1
by_hand = {kind: total[kind] / count[kind] for kind in sorted(total)}
for kind, average in by_hand.items():
print(f"{kind:10} {average:9.2f}")
print()
print("== pandas: бір жол")
df = pd.DataFrame(rows, columns=["kind", "amount"])
by_pandas = df.groupby("kind")["amount"].mean()
print(by_pandas)
print()
print("== жауаптар сәйкес:", all(round(by_hand[k], 6) == round(by_pandas[k], 6) for k in by_hand))
print("кесте:", type(df).__name__, "| бағандар:", list(df.columns))
print("баған түрлері:", dict(df.dtypes.astype(str)))
print("жауап:", type(by_pandas).__name__, "— тізім емес, белгісі бар баған")
print()
print("== баған тұтас, циклсіз")
with_vat = df["amount"] * 1.12
print("pandas:", [round(x, 2) for x in with_vat.head(3)])
print("қолмен:", [round(amount * 1.12, 2) for _, amount in rows[:3]])
Шығысы:
жол саны: 300000
== қолмен: екі сөздік және бір өту
байланыс 3998.49
басқа 1500.48
жанармай 8999.49
пәтерақы 60001.50
тамақ 2497.49
== pandas: бір жол
kind
байланыс 3998.490
басқа 1500.480
жанармай 8999.490
пәтерақы 60001.495
тамақ 2497.485
Name: amount, dtype: float64
== жауаптар сәйкес: True
кесте: DataFrame | бағандар: ['kind', 'amount']
баған түрлері: {'kind': 'str', 'amount': 'int64'}
жауап: Series — тізім емес, белгісі бар баған
== баған тұтас, циклсіз
pandas: [2296.0, 9617.44, 66778.88]
қолмен: [2296.0, 9617.44, 66778.88]
Талдау
Алдымен бірдей жауап, содан кейін секундомер
Әртүрлі нәрсені санайтын екі бағдарламаның жылдамдығын салыстыру — өлшеудегі ең жиі кездесетін қате. Сондықтан zamer.py бірінші істейтіні — екі тәсілдің де бір нәрсе бергенін дәлелдеу. Керек емес нәрсені тез санауды кез келген адам біледі.
Салыстыру == арқылы емес, round(..., 6) арқылы жүреді: бұл бөлшек сандар, ал оларды тікелей салыстыруға неге болмайтынын төртінші сабақ көрсеткен.
DataFrame деген шын мәнінде не
Кортеждер тізімі жолдарды сақтайды: үш жүз мың кішкентай нысан, әрқайсысының өз тақырыбы мен өз сілтемелері бар. DataFrame бағандарды сақтайды: үш жүз мың сома бір типтегі сандардың бір блогында, қатарынан, орамасыз жатады. Қалғанының бәрі осыдан шығады.
Сондықтан df["amount"] * 1.12 үш жүз мың Python нысанын бір-бірлеп аралап шықпайды. Ол блокты тұтасымен Python-да жазылмаған кодқа береді, көбейту сонда жүреді. Цикл жоғалған жоқ — ол жай ғана Python циклі болудан қалды.
Series де осыдан. Топтаудың жауабы — сандар тізімі емес, белгісі бар баған: сол жағында санаттар, оң жағында мәндер, әрі олар бірге ұсталады. groupby-дан кейінгі тізімге белгіні өзіңіз қоюға тура келер еді — дәл сол қолмен жазған сөздікпен.
Өлшем
Сол кесте, бірақ миллион жолда, төрт сұрақ және timeit. Бағдарлама дұрыстық туралы ештеңе баспайды — дұрыстық жоғарыда дәлелденді:
# өлшем: бір сұрақ, екі тәсілмен қойылған. Сандар менікі емес, сіздікі болады.
import timeit
import pandas as pd
KINDS = ["тамақ", "жанармай", "пәтерақы", "байланыс", "басқа"]
BASE = {"тамақ": 2500, "жанармай": 9000, "пәтерақы": 60000, "байланыс": 4000, "басқа": 1500}
rows = [(KINDS[i % 5], BASE[KINDS[i % 5]] + (i * 37) % 900 - 450) for i in range(1_000_000)]
df = pd.DataFrame(rows, columns=["kind", "amount"])
def hand_group():
total, count = {}, {}
for kind, amount in rows:
total[kind] = total.get(kind, 0) + amount
count[kind] = count.get(kind, 0) + 1
return {k: total[k] / count[k] for k in total}
def best(work):
# Бес өлшем, ең жақсысын аламыз: баяулары — машина басқа іспен алаңдағаны.
return min(timeit.repeat(work, number=1, repeat=5))
print("орташа чек: қолмен %.3f с, pandas %.3f с"
% (best(hand_group), best(lambda: df.groupby("kind")["amount"].mean())))
print("жаңа баған: қолмен %.3f с, pandas %.3f с"
% (best(lambda: [a * 1.12 for _, a in rows]), best(lambda: df["amount"] * 1.12)))
print("бір санаттың сомасы: қолмен %.3f с, pandas %.3f с"
% (best(lambda: sum(a for k, a in rows if k == "тамақ")),
best(lambda: df.loc[df["kind"] == "тамақ", "amount"].sum())))
print("сұрыптау: қолмен %.3f с, pandas %.3f с"
% (best(lambda: sorted(rows, key=lambda r: r[1])), best(lambda: df.sort_values("amount"))))
Менің машинамда — Python 3.14.5, pandas 3.0.5, миллион жол:
| Не санаймыз | Қолмен | pandas |
|---|---|---|
| санат бойынша орташа чек | 0.124 с | 0.062 с |
| 12 % үстемесі бар баған | 0.045 с | 0.001 с |
| бір санат бойынша сома | 0.021 с | 0.045 с |
| сома бойынша сұрыптау | 0.173 с | 0.089 с |
| сол кестенің жады | 100 МБ | 155 МБ |
Сіздің сандарыңыз басқаша болады: басқа машина, pandas-тың басқа нұсқасы, бағандардың басқа түрлері, басқа жүктеме. Бағандар арасындағы қашықтық та өзгереді — бір жерде көбірек, бір жерде азырақ. Мұнда тұрақтысы басқа: қай амалда айырма ретке кетеді, ал қайсысында есе күйінде қалады. Есте ұстауға тұратыны осы, ал сандарды өзіңізде қайта өлшеген жөн.
Осы сандардан не көрінеді
Қырық есе — баған санайтын жерде. df["amount"] * 1.12 тізім өрнегіне қарсы: 0.001 және 0.045. Миллион көбейту бір-бірлеп емес, блокпен бір рет жасалды. pandas үшін басты дәлел осы, әрі есеп ретпен жүретін жалғыз жер де осы.
Екі есе — бәрін бәрібір аралап шығу керек жерде. Топтау мен сұрыптау қырық емес, екі есе ұтады. Жұмыс сол күйінде қалады, оны жай ғана біздікінен жылдамырақ код орындайды. Екі есе — жақсы, бірақ кітапхана орнататын айырмашылық емес.
Баяу — артық нәрсе сұраған жерде. df.loc[df["kind"] == "тамақ", "amount"].sum() алдымен маска құрады: миллион «иә/жоқ» жауабы, тұтас жаңа баған, — содан кейін ғана қосады. Ал sum(a for k, a in rows if k == "тамақ") бір рет өтеді және ештеңе құрмайды. Бір сұрақта тізім ұтады. Ұтыс маска сақталып, қайта қолданылғанда пайда болады: tamaq = df["kind"] == "тамақ", сосын df.loc[tamaq, "amount"].sum(), df.loc[tamaq, "amount"].mean(), df.loc[tamaq] — миллион салыстыру он сұраққа бір рет жасалады. Шартты әр жолы қайта жазсаңыз, pandas масканы да қайта құрады.
Жады тегін емес. Сол кесте: тізімде жүз мегабайт, кестеде жүз елу бес. Кінәлі сандар емес, сөздер: kind бағанында бес түрлі мән бар, бірақ олар әр жолға бір данадан жатыр. df["kind"].astype("category") осы 155 МБ-ты 9 МБ-қа айналдырады — бес сөз бір рет сақталады, бағанда олардың нөмірлері ғана қалады. Бұл жиындар туралы сабақтағы тәсілдің дәл өзі: бірдей нәрсені екі рет сақтамау.
Импорт үштен бір секунд тұрады. Бір минут жұмыс істейтін бағдарлама үшін бұл ештеңе емес. Минут сайын кесте бойынша қосылатын бағдарлама үшін бұл оның басты шығыны — әрі pandas-ты шағын скриптке тартпауға себеп.
Оны алудың нағыз себебі
Қолмен сегіз жол, pandas-та бір — шешетіні шын мәнінде осы. Секунд емес, сол кестеге қойылатын екінші, үшінші және оныншы сұрақтың әрқайсысы бір жолға түсетіні, әрі жауаптың келесі сұрақ қоюға болатын кесте күйінде қалатыны.
Дәл осы дәлелді SQL туралы сабақта көргенсіз: GROUP BY циклден жылдам болғаны үшін емес, сұрақ сұрақ болып жазылғаны үшін жақсы. pandas — сол қадам, тек кесте дерекқорда емес, бағдарламаңыздың жадында жатыр.
Оны қашан алмау керек
- Бес жүз жол және бір сұрақ: цикл қарапайымырақ, ал импорт жұмыстың өзінен ұзақ.
- Дерек кесте емес: кірістірілген JSON немесе ағашты алдымен жазық болуға көндіру керек.
- Өмірде бір рет санайсыз: орнатып, нұсқасын бекітіп қоятын тәуелділік тапсырмадан ұзақ жасайды.
Жұмыс істейтін ереже: кесте бар және оған бір сұрақтан көп қойылады — pandas алыңыз; жауап біреу және дерек аз — алмаңыз.
Сабақ картасы
Өз сөзіңізбен айтыңыз
Қарамай, дауыстап немесе қағазға жауап беріңіз. Жауаптар — сабақтың соңында.
- Бағдарлама неге алдымен жауаптардың сәйкес келгенін дәлелдеп, содан кейін ғана уақыт өлшейді?
- Бағанды көбейту неге циклден қырық есе жылдам, ал топтау — тек екі есе?
- pandas қай жағдайда кәдімгі тізімнен баяу болып шықты және неге?
Жаттығу
Тапсырмаға дейінгі үш қысқа қадам: болжау, толықтыру, түзету. Жауаптар — сабақтың соңында, бірақ алдымен өзіңіз жауап беріңіз.
1. Болжаңыз. Бұл бағдарлама не басып шығарады?
import pandas as pd
cheques = pd.Series([1200, 800, 4000], index=["тамақ", "тамақ", "байланыс"])
print(cheques * 1.12)
print("сома:", cheques.sum(), "| орташа:", cheques.mean())
2. Бос орынды толтырыңыз. ... орнына len(times) 5 басып шығаратындай өлшем санын қойыңыз.
# қанша рет өлшеу керек: бір өлшем — өлшем емес
import timeit
times = timeit.repeat(lambda: sum(range(100_000)), number=1, repeat=...)
print("өлшем саны:", len(times), "| ең жақсысы ең нашарынан аспайды:", min(times) <= max(times))
3. Түзетіңіз. Екі тәсіл бір кестенің жолдарын санап, біреуіне айырмашылық берді. Артық жолды тауып, алып тастаңыз.
# қолмен pandas-тағыдан бір жол артық шығады
import csv
import io
import pandas as pd
TEXT = "kind,amount\nтамақ,1200\nтамақ,800\nбайланыс,4000\n"
reader = csv.reader(io.StringIO(TEXT))
print("қолмен жол саны:", len(list(reader)))
print("pandas жол саны:", len(pd.read_csv(io.StringIO(TEXT))))
Тапсырма
Міндетті. Сабақтағы сол 300 000 жолдық кестені алыңыз. Әр санат бойынша үш санды есептеңіз: соманы, орташа чекті және жалпы сомадағы үлесті. Алдымен қолмен — бір өтуде сөздіктермен, — содан кейін pandas арқылы: df.groupby("kind")["amount"].agg(["sum", "mean"]) және үлес бағаны.
Сома бойынша жоғарыдан төмен сұрыпталған кестені, «барлығы» жолын және салыстыру жолын басып шығарыңыз. Салыстыру алтыншы белгіге дейін сәйкес келуге тиіс.
Күтілетін шығыс:
санат сома орташа үлес
пәтерақы 3600089700 60001.50 77.9%
жанармай 539969400 8999.49 11.7%
байланыс 239909400 3998.49 5.2%
тамақ 149849100 2497.49 3.2%
басқа 90028800 1500.48 1.9%
барлығы 4619846400
сәйкес келеді: иә
Дайын болғаны: шығыс жол-жолымен сәйкес келеді; үлестер орташалардың сомасынан емес, жалпы сомадан есептеледі; салыстыру бөлшек сандарды тікелей емес, дөңгелектелген түрде салыстырады; кесте әліпби бойынша емес, сома бойынша сұрыпталған.
Өз деректеріңізде. Өз файлыңызды алыңыз — банк экспортын, шығыс кестесін, бірнеше мың жолдық кез келген нәрсені — және оған бір сұрақты екі тәсілмен қойыңыз. timeit арқылы өлшеңіз. Екі санды жазып алып, өзіңізге жауап беріңіз: дәл сіздің деректеріңізде кітапхана осы айырмашылыққа тұра ма.
Қалауыңыз бойынша.
- Машинаңызда
import pandasөзі қанша тұратынын өлшеңіз:python -X importtime -c "import pandas". - Кестені он есе үлкейтіп, төрт айырмашылықтың қайсысы өскенін, қайсысы бұрынғы қалпында қалғанын қараңыз.
kindбағанынastype("category")түріне ауыстырып,df.memory_usage(deep=True).sum()мәнін бұрын және кейін салыстырыңыз.
Жобада бұл қайда тұрады
Әзірге еш жерде — және бұл әдейі. Сабақ «тұра ма» деген сұраққа жауап береді, «қалай» дегенге емес; жиынтық келесі сабақтан бастап өзгереді, сонда оның кестесі DataFrame болады. Қазір қосылатын жалғыз нәрсе — жобаның requirements.txt файлындағы pandas==3.0.5 жолы: нұсқа бекітілген, өйткені сабақтардағы шығыс дәл сонымен басылған.
Қарыздар. Кітапхана — бұл тәуелділік: оны орнату, жаңарту және бір күні жаңартудан кейін жөндеу керек. Жиынтық жолдары мыңдап саналатын валюта бағамдарын есептеп тұрғанда, ондағы pandas ұтысы — жылдамдық емес, қысқа код. Мұны мойындаған адал болады, жеделдеттік деп көрсеткеннен гөрі.
Жауаптар
Жауаптарды көрсету
Сұрақтарға
- Өйткені екі бағдарламаның жылдамдығын салыстыруға тек олар бір жауап берген жағдайда ғана болады. Әйтпесе өлшем басқа нәрсені өлшейді: дұрыс санамайтын бағдарлама әдетте жылдамырақ та болады — ол жұмысты азырақ істейді.
- Бағанды тұтас көбейту Python-нан тыс кодқа кетеді және жады блогымен қатарынан жүреді, сондықтан ретпен ұтады. Топтау әр жолды бәрібір көріп, оны себеттерге салуға міндетті: жұмыс сол күйінде, тек жылдамырақ орындалады — «қырық есе» емес, «екі есе» содан.
- Бір санат бойынша сомада.
df["kind"] == "тамақ"миллион «иә/жоқ» мәнінен тұтас жаңа баған құрады, содан кейін ғана қосады, ал генератор деректі бір рет аралап өтеді және ештеңе жасамайды. Кестеге қойылатын сұрақ көбейе салысымен бұл төлем өтеледі.
Жаттығуға
- Көбейту әр мәнге қолданылады, белгілер орнында қалады, ал бағанның түрі бөлшекке айналады.
sum()менmean()бүкіл бағанды бірден санайды.
тамақ 1344.0
тамақ 896.0
байланыс 4480.0
dtype: float64
сома: 6000 | орташа: 2000.0
repeat=5.timeit.repeatқанша сұрасаңыз, сонша өлшемнен тұратын тізім қайтарады; олардан орташа емес, ең кішісі алынады: артық уақыт — бұл әрқашан сол машинадағы басқа жұмыс, сіздің бағдарламаңыз емес.
import timeit
times = timeit.repeat(lambda: sum(range(100_000)), number=1, repeat=5)
print("өлшем саны:", len(times), "| ең жақсысы ең нашарынан аспайды:", min(times) <= max(times))
өлшем саны: 5 | ең жақсысы ең нашарынан аспайды: True
- Артық жол — тақырып.
csv.readerоны кәдімгі дерек жолы ретінде береді, алread_csvоның баған атаулары екенін түсінеді. Оныnext(reader)арқылы өткізіп жіберіңіз.
import csv
import io
import pandas as pd
TEXT = "kind,amount\nтамақ,1200\nтамақ,800\nбайланыс,4000\n"
reader = csv.reader(io.StringIO(TEXT))
next(reader)
print("қолмен жол саны:", len(list(reader)))
print("pandas жол саны:", len(pd.read_csv(io.StringIO(TEXT))))
қолмен жол саны: 3
pandas жол саны: 3
Бұл сонымен бірге он екінші сабақтағы csv модулі бар кезде read_csv не үшін керек деген сұраққа жауап: ол тақырыпты да, баған түрлерін де, жетіспейтін мәндерді де біледі — яғни циклде өзіңіз есте сақтауға тиіс нәрсенің бәрін.
Тапсырмаға
Үлесті орташалардың сомасынан емес, сомалардың сомасынан санау керек: санаттар әртүрлі көлемде, ал орташалардың орташасы басқа жауап береді. pandas-та бұл table["sum"] / table["sum"].sum() * 100, ал қолмен — шығару циклінен бұрын бір рет есептелген sum(total.values()) мәніне бөлу.
Сома бойынша сұрыптау — table.sort_values("sum", ascending=False). Онсыз кесте әліпби бойынша шығады, өйткені groupby кілт бойынша сұрыптайды.
Дереккөздер
- pandas-ке он минут — ресми кіріспе, оның алғашқы екі бөлімін оқыған жөн.
- pandas 3.0-де не жаңалық — осы сабақтың шығысы басылған нұсқа.
- timeit модулі — машинаны емес, бағдарламаны өлшеу үшін уақытты қалай өлшеу керек.
- pandas: үлкен деректер — кесте жадыға сыймай қалғанда не істеу керек.
Пікірлер (0)
Пікір қалдыру үшін кіріңіз →
Әзірге пікір жоқ. Бірінші болыңыз.