কলাম এবং সারি বেছে নেওয়া — নাম দিয়ে কিংবা অবস্থান দিয়ে
টেবিলের নির্দিষ্ট অংশে নির্দেশ করা: ব্র্যাকেট দিয়ে কলাম, loc দিয়ে লেবেল অনুযায়ী ও iloc দিয়ে অবস্থান অনুযায়ী সারি, at দিয়ে একক ঘর — এবং আপনি কোনটি ব্যবহার করছেন তা সবসময় স্পষ্ট জানা।
- 1সমস্যা
- 2বোঝা
- 3উদাহরণ
- 4অনুমান
- 5নিজে করা
- 6কঠিন করা
যে সমস্যাটা আমরা সমাধান করছি
অধ্যায় তিনে যেভাবে শেখানো হয়েছিল, সেভাবে আপনি দোকানের অর্ডারের ফাইলটি পড়েছেন এবং যাচাই করে নিয়েছেন। এবার মানুষজন এটি নিয়ে নানা প্রশ্ন করতে শুরু করল।
স্টক টিম বলল: "আমাদের প্রতিটি অর্ডারের শুধু প্রডাক্ট আর পরিমাণ (quantity) পাঠিয়ে দিন — অন্য কিছু লাগবে না।" হিসাবরক্ষক বললেন: "১০০৬ নম্বর অর্ডারটি ঠিক কী ছিল, আর সেটার দাম কত?" আর আপনার ম্যানেজার বললেন: "সবশেষে যে তিনটি অর্ডার এসেছে, সেগুলো আমাকে দেখান।"
এগুলোর কোনোটিই পুরো টেবিল জুড়ে কোনো হিসাবনিকাশ নয়। প্রতিটির মূল কাজ হলো নির্দিষ্ট জায়গায় নির্দেশ করা (pointing): কিছু কলামের দিকে, একটি নির্দিষ্ট সারির দিকে, শেষের কয়েকটি সারির দিকে, কিংবা একটি একক ঘরের (cell) দিকে। পুরো টেবিলটা স্ক্রিনে প্রিন্ট করে আঙুল দিয়ে দেখে নেওয়া হয়তো আটটি সারির জন্য চলে। কিন্তু আট হাজার সারির ক্ষেত্রে সেটা চলে না, আর কোনো প্রোগ্রামের ভেতরেও সেটা চলে না — কারণ প্রোগ্রামের কোনো আঙুল নেই।
পান্ডাসে কোনো কিছু নির্দেশ করার কয়েকটি উপায় রয়েছে, আর কেন একাধিক উপায় রয়েছে — সেটাই এই অধ্যায়ের আসল বিষয়। একটি সারির দিকে দুটি ভিন্ন উপায়ে নির্দেশ করা যায়: তার নাম দিয়ে (ইনডেক্সে থাকা তার লেবেল) অথবা তার অবস্থান দিয়ে (প্রথম, দ্বিতীয়, শেষ)। সদ্য পড়া কোনো টেবিলে কাকতালীয়ভাবে এই দুটোই একই সংখ্যা হয়, তাই নতুনরা কখনোই খেয়াল করেন না যে এখানে দুটি আলাদা বিষয় রয়েছে। কিন্তু যেই মুহূর্তে আপনি শেষের তিনটি সারি আলাদা করবেন, কিংবা সর্ট বা ফিল্টার করবেন, তখনই তারা আর এক থাকে না — আর যে কোডে এই দুটোকে গুলিয়ে ফেলা হয়েছে, সেটি কোনো ত্রুটি (error) না দেখিয়েই নীরবে ভুল সারি দেখাতে শুরু করে।
তাই এই অধ্যায়টি সিলেকশন বা নির্দিষ্ট অংশ বেছে নেওয়ার বিষয়ে, তবে সবচেয়ে বড় কথা — আপনি এই দুটির কোনটি ব্যবহার করছেন, তা সবসময় স্পষ্টভাবে জেনে রাখা।
এই অধ্যায় শেষে আপনি পারবেন
- একটি কলাম বেছে নিয়ে
Series, কিংবা একাধিক কলাম বেছে নিয়েDataFrameপেতে — এবং বুঝতে পারবেন আপনার হাতে কোনটি এসেছে - লেবেল দিয়ে
.locএবং অবস্থান দিয়ে.ilocব্যবহার করে সারি নির্বাচন করতে, এবং তাদের স্লাইসের সমাপ্তি কেন ভিন্ন তা ব্যাখ্যা করতে .loc[rows, columns]দিয়ে সারি ও কলাম একসাথে নির্বাচন করতে এবং ফলাফলের আকার (shape) আগে থেকেই অনুমান করতে — এমনকি.locবা.atদিয়ে একটি একক মান বের করতেset_index()দিয়ে সারিগুলোকে অর্থপূর্ণ লেবেল দিতে, এবং তা.loc-এর অর্থ কীভাবে বদলে দেয় তা বুঝতে- পান্ডাস ৩-এ কোনো সিলেকশন পরিবর্তন করলে কেন মূল টেবিল পরিবর্তিত হয় না তা ব্যাখ্যা করতে, এবং মূল টেবিলে সঠিকভাবে পরিবর্তন আনতে; সিলেকশনের সময় ঘটা
KeyErrorওIndexErrorচিনে ঠিক করতে
আগে যা জানা লাগবে: CSV ফাইল পড়া, আর পড়ার পর যাচাই করা।
আগে ফাইলটি তৈরি করে নিন
আপনার প্রজেক্ট ফোল্ডারে ঠিক এই নয়টি লাইন দিয়ে orders.csv নামে একটি ফাইল তৈরি করুন। এই ফাইলটি পুরো কোর্সের বাকি অংশে ব্যবহৃত হবে, তাই এমন কোথাও রাখুন যেন পরে সহজেই খুঁজে পান:
order_id,date,branch,product,category,quantity,price
1001,2024-03-01,north,pen,stationery,12,15.0
1002,2024-03-01,south,notebook,stationery,5,60.0
1003,2024-03-02,north,bag,accessories,2,850.0
1004,2024-03-02,east,bottle,accessories,7,120.0
1005,2024-03-03,north,eraser,stationery,30,8.0
1006,2024-03-03,south,bag,accessories,1,850.0
1007,2024-03-04,east,pen,stationery,20,15.0
1008,2024-03-04,north,bottle,accessories,4,120.0প্রতিটি লাইন একটি করে অর্ডার: অর্ডার নম্বর, তারিখ, দোকানের যে শাখা থেকে বিক্রি হয়েছে, কী বিক্রি হয়েছে, পণ্যের ক্যাটাগরি, কয়টি, এবং একটি পণ্যের দাম।
কোড লেখার আগে
সিলেকশনের কাজ দেখতে এতো ছোট মনে হয় যে মানুষ সাথে সাথেই ব্র্যাকেট টাইপ করতে শুরু করে দেয়। আগে এই ধাপগুলোতে এক মিনিট সময় দিন, কারণ এই অধ্যায়ের প্রতিটি সিলেকশন সংক্রান্ত ভুল এগুলোর কোনো একটি বাদ দেওয়ার কারণেই ঘটে।
১. প্রশ্নটি এক বাক্যে বলুন, এবং বিশেষ্যগুলোর (nouns) নিচে দাগ দিন। বিশেষ্যগুলোই বলে দেয় আপনি কোনটির দিকে নির্দেশ করছেন:
- "প্রতিটি অর্ডারের product এবং quantity" — দুটি কলাম, সব সারি
- "order 1006" — একটি সারি, তার অর্ডার নম্বর দিয়ে চিহ্নিত
- "last three অর্ডার" — অবস্থান অনুযায়ী সারি, শেষ দিক থেকে গোনা
- "order 1006-এর price" — একটি ঘর (cell): একটি সারি ও একটি কলাম একসাথে
২. টেবিলে নির্দেশ করার আগে টেবিলটি দেখে নিন। আপনি শুধু বিদ্যমান নামগুলোর দিকেই নির্দেশ করতে পারেন। সিলেকশনের জন্য তিনটি বিষয় জরুরি: আকার (size), কলামগুলোর নিখুঁত নাম, এবং ইনডেক্স — অর্থাৎ বাঁ পাশের লেবেলগুলো।
import pandas as pd
orders = pd.read_csv("orders.csv")
print(orders.shape)
print(list(orders.columns))
print(orders.index)(8, 7)
['order_id', 'date', 'branch', 'product', 'category', 'quantity', 'price']
RangeIndex(start=0, stop=8, step=1)কলামের নামগুলো কোটেশনের ভেতরে একটি তালিকা হিসেবে আসে — যাতে আপনি দেখতে পান 'price' ছোট হাতের অক্ষরে লেখা এবং এতে কোনো বাড়তি স্পেস নেই। ইনডেক্সটি হলো ০ থেকে ৭ পর্যন্ত একটি RangeIndex: লেবেলগুলো স্রেফ গণনা মাত্র। এটি মনে রাখুন, কারণ এই মিলটি নিয়েই পুরো অধ্যায়টি সাজানো। এই মুহূর্তে যে সারিটির লেবেল ২, সেই সারিটির অবস্থানও ২। এটি কিন্তু সবসময় সত্য থাকবে না।
৩. ফলাফল পাওয়ার আগেই তার রূপ কল্পনা করুন (sketch)। কী ফলাফল আসা উচিত — একটি একক সংখ্যা, একটি কলাম, নাকি একটি ছোট টেবিল? কয়টি সারি এবং কয়টি কলাম? স্টক টিমের অনুরোধের উত্তর হলো ৮ সারি ও ২ কলামের একটি টেবিল। "order 1006"-এর জন্য এটি একটি একক সারি। আর "order 1006-এর দাম"-এর জন্য এটি একটি একক সংখ্যা, 850.0। আপনি যদি প্রত্যাশিত আকারটি জানেন, তবে ফলাফল দেখার সাথে সাথেই ভুল সিলেকশন ধরা পড়ে যাবে।
৪. আপনার জানার ওপর ভিত্তি করে টুল বেছে নিন। এই তালিকাটি হলো পুরো অধ্যায়ের একটি রূপরেখা:
- আপনার জানা আছে কলামের নাম → থার্ড ব্র্যাকেট:
orders["quantity"],orders[["product", "quantity"]] - আপনার জানা আছে সারির লেবেল, ইনডেক্সে তার নাম →
.loc:orders.loc[2] - আপনার জানা আছে সারির অবস্থান — প্রথম, শেষ, পঞ্চম →
.iloc:orders.iloc[-3:] - আপনার সারি ও কলাম একসাথে প্রয়োজন →
.loc[rows, cols]বা.iloc[rows, cols]:orders.loc[2:4, ["product", "price"]] - আপনার প্রয়োজন ঠিক একটি ঘর →
.at[row, col]বা.loc[row, col]:orders.at[5, "price"]
৫. কীভাবে যাচাই করবেন তা ঠিক করে নিন। যেকোনো সিলেকশনের পর ফলাফলের .shape বা type() প্রিন্ট করে আপনার কল্পনার সাথে মিলিয়ে নিন। এটি মাত্র এক লাইনের কোড, কিন্তু নিচে উল্লেখিত বেশিরভাগ ভুলকে সংখ্যায় রূপান্তরিত হওয়ার আগেই আটকে দেয়।
অধ্যায়ের বাকি অংশে এই টুলগুলো একটি একটি করে আলোচনা করা হয়েছে, প্রতিটির সাথে রয়েছে সমাধানসহ উদাহরণ।
একটি কলাম: থার্ড ব্র্যাকেটে নাম
থার্ড ব্র্যাকেটে একটি কলামের নাম লিখলে আপনি সেই কলামটি পেয়ে যাবেন:
quantity = orders["quantity"]
print(type(quantity))
print(quantity)<class 'pandas.Series'>
0 12
1 5
2 2
3 7
4 30
5 1
6 20
7 4
Name: quantity, dtype: int64ফলাফল হিসেবে যা আসে তা একটি Series — অধ্যায় দুইয়ের ঠিক সেই অবজেক্টটি: মানগুলো, বাঁ পাশে একটি ইনডেক্স, একটি Name (যে কলাম থেকে এটি এসেছে) এবং একটি dtype। খেয়াল করুন, ইনডেক্সটিও এর সাথে এসেছে। 0 থেকে 7 কোনো সাজসজ্জা নয়: এগুলো এখনও বলে দেয় প্রতিটি সংখ্যা কোন সারির অংশ।
যেহেতু এটি একটি Series, তাই প্রতিটি Series মেথড এতে সরাসরি কাজ করে:
print(orders["quantity"].sum())81সাধারণত একটি কলাম বেছে নেওয়ার কারণ এটাই: সেটির কাছে কোনো নির্দিষ্ট প্রশ্ন করা বা হিসাব করা।
একাধিক কলাম: ব্র্যাকেটের ভেতরে একটি তালিকা
স্টক টিমের জন্য আপনার দুটি কলাম দরকার। ব্র্যাকেটের ভেতরে নামের একটি তালিকা (list) দিন:
stock = orders[["product", "quantity"]]
print(type(stock))
print(stock)<class 'pandas.DataFrame'>
product quantity
0 pen 12
1 notebook 5
2 bag 2
3 bottle 7
4 eraser 30
5 bag 1
6 pen 20
7 bottle 4ডাবল ব্র্যাকেট কোনো বিশেষ সিনট্যাক্স নয়, এবং এটি স্পষ্টভাবে বুঝে নেওয়া দরকার। বাইরের জোড়া ব্র্যাকেট হলো সিলেকশনের জন্য; ভেতরের জোড়া হলো সাধারণ পাইথন তালিকা, ["product", "quantity"]। আপনি চাইলে আগে তালিকাটি তৈরি করে তারপর তা দিতে পারতেন — wanted = ["product", "quantity"] এবং তারপর orders[wanted] — এর অর্থও হতো হুবহু এক। কলামগুলো তালিকার ক্রম অনুযায়ী ফিরে আসে, তাই কলামের ক্রম পরিবর্তনের জন্যও এটি ব্যবহার করা হয়।
ফলাফলটি একটি DataFrame, কারণ দুটি কলামের টেবিল আসলে একটি টেবিলই। আর এখানে এমন একটি পার্থক্য রয়েছে যা মানুষকে মাসের পর পর বিভ্রান্ত করে: একটিমাত্র নাম থাকা একটি তালিকা দিলেও কিন্তু ফলাফল হিসেবে একটি DataFrame পাওয়া যায়।
print(orders["product"].shape)
print(orders[["product"]].shape)(8,)
(8, 1)(8,) হলো একটি Series — এক মাত্রা (one dimension), আটটি মান। আর (8, 1) হলো আট সারি এবং এক কলামের একটি DataFrame। তারা ভিন্নভাবে প্রিন্ট হয়, তাদের মেথড ভিন্ন, এবং ফাইলে সেভ করার সময় DataFrame তার কলামের হেডার বজায় রাখে। নিয়মটি সহজ: একটি নাম দিলে Series পাওয়া যায়; একটি তালিকা দিলে DataFrame পাওয়া যায় — তালিকায় যতগুলো নামই থাকুক না কেন।
orders.quantity কেন নয়?
অনেক টিউটোরিয়ালে আপনি ডট দিয়ে কলাম অ্যাক্সেস করতে দেখবেন:
print(orders.branch.head(2))0 north
1 south
Name: branch, dtype: strএখানে এটি কাজ করছে, এবং এটি ছোট। কিন্তু এটি তখনই কাজ করে যখন কলামের নামটি ঘটনাক্রমে পাইথনের একটি বৈধ ভেরিয়েবল নাম হয় এবং যা ইতিমধ্যে অন্য কোনো কাজে ব্যবহৃত হয়নি — অথচ একটি DataFrame-এ শত শত মেথড ও অ্যাট্রিবিউট থাকে। নিচে একটি ছোট টেবিল দেখুন যার কলামগুলোর নাম count এবং size, দোকানের জন্য দুটি খুবই যৌক্তিক নাম:
d = pd.DataFrame({"count": [1, 2], "size": [3, 4]})
print(d.size)
print(d["size"])4
0 3
1 4
Name: size, dtype: int64d.size আপনাকে কলামটি মোটেও দেয়নি। এটি কোনো ত্রুটি না দেখিয়ে নীরবে DataFrame-এর নিজস্ব size অ্যাট্রিবিউট ফেরত দিয়েছে — ঘরের মোট সংখ্যা, ২ × ২ = ৪। কোনো এরর নেই, স্রেফ একটি ভুল উত্তর। আবার যে নামে স্পেস রয়েছে, যেমন unit price, সেটি তো ডট দিয়ে লেখাই যায় না। থার্ড ব্র্যাকেটের ক্ষেত্রে এই সমস্যাগুলোর কোনোটিই নেই, তাই কোড লেখার নিয়ম হলো: সবসময় df["col"] ব্যবহার করুন। টার্মিনালে চটজলদি একবার দেখে নেওয়ার জন্য ডট ঠিক আছে, এর বেশি কিছু নয়।
লেবেল দিয়ে সারি: .loc
সারি সাধারণ ব্র্যাকেট দিয়ে নির্বাচন করা যায় না। (সাধারণ ব্র্যাকেটে নাম লেখার অর্থ হলো "কলাম", এবং আপনি কোনটি বুঝিয়েছেন তা পান্ডাস নিজে থেকে আন্দাজ করবে না।) সারির জন্য দুটি আলাদা টুল রয়েছে, যার প্রথমটি হলো .loc — label-based location (লেবেল-ভিত্তিক অবস্থান)। আপনি ইনডেক্স থেকে লেবেলটি এতে দেবেন:
print(orders.loc[2])order_id 1003
date 2024-03-02
branch north
product bag
category accessories
quantity 2
price 850.0
Name: 2, dtype: objectএকটি সারি ফিরে আসে কাৎ করা একটি Series হিসেবে: কলামের নামগুলো হয়ে গেছে এর ইনডেক্স, আর এর Name হলো সারিটির লেবেল, 2।
dtype: object-এর দিকে তাকান। একটি কলামে একটি নির্দিষ্ট ধরন থাকে, কিন্তু একটি সারি বিভিন্ন কলাম জুড়ে বিস্তৃত — একটি সংখ্যা, কিছু লেখা, একটি দশমিক সংখ্যা — তাই এগুলোর সব কটি ধারণ করার মতো একমাত্র ধরন হলো সাধারণ object। পান্ডাস যে কলামভিত্তিক চিন্তা করে এটি তার একটি কারণ: সারিতে ধরনের মিশ্রণ থাকে, কিন্তু কলামে থাকে না।
এবার গুরুত্বপূর্ণ অংশটি দেখুন। .loc-এর কাছে লেবেলের একটি রেঞ্জ চান:
print(orders.loc[2:4])order_id date branch product category quantity price
2 1003 2024-03-02 north bag accessories 2 850.0
3 1004 2024-03-02 east bottle accessories 7 120.0
4 1005 2024-03-03 north eraser stationery 30 8.0তিনটি সারি: ২, ৩ এবং ৪। একটি .loc স্লাইস তার শেষ মানটিকেও অন্তর্ভুক্ত করে। পাইথন জানা প্রত্যেকেই এতে অবাক হন, কারণ পাইথনে range(2, 4) এবং list[2:4] শেষ মান ৪-এর আগে থেমে যায়। এটি কিন্তু কোনো অসঙ্গতি নয়; এর পেছনে একটি সুনির্দিষ্ট কারণ আছে।
লেবেল সবসময় সংখ্যা হয় না। লেবেলগুলো যদি পণ্যের নাম হতো, তবে আপনি লিখতেন loc["bag":"eraser"]-এর মতো কিছু। শেষ মানটিকে বাদ দিতে হলে "eraser"-এর পরের লেবেলটির নাম আপনাকে লিখতে হতো — আর নামের ক্ষেত্রে আগে থেকে না দেখে "পরবর্তী নাম" কোনটি তা জানা অসম্ভব। তাই লেবেলের ক্ষেত্রে আপনি যেখান থেকে শুরু করতে চান এবং যেখানে শেষ করতে চান দুটোই বলে দেন, আর আপনি দুটোই পান। কিন্তু অবস্থানের ক্ষেত্রে বিষয়টি আলাদা: ৪-এর পরের অবস্থান সবসময়ই ৫, তাই সেখানে পাইথনের "শেষের আগে থামার" নিয়মে কোনো সমস্যা হয় না।
অবস্থান দিয়ে সারি: .iloc
দ্বিতীয় টুলটি হলো .iloc — integer location (পূর্ণসংখ্যা-ভিত্তিক অবস্থান)। এটি লেবেলগুলোকে পুরোপুরি উপেক্ষা করে এবং ঠিক পাইথন লিস্টের মতো ০ থেকে স্থান গণনা করে:
print(orders.iloc[2:4])order_id date branch product category quantity price
2 1003 2024-03-02 north bag accessories 2 850.0
3 1004 2024-03-02 east bottle accessories 7 120.0দুটি সারি, অবস্থান ২ এবং ৩; পাইথনের সাধারণ নিয়মের মতো অবস্থান ৪ এখানে বাদ পড়েছে। দুটি আউটপুট পাশাপাশি রেখে দেখুন: একই টেবিলে, ব্র্যাকেটের ভেতরে একই সংখ্যা থাকা সত্ত্বেও loc[2:4] দিয়েছে তিনটি সারি আর iloc[2:4] দিয়েছে দুটি সারি।
যেহেতু .iloc অবস্থানভিত্তিক, তাই ঋণাত্মক সংখ্যা শেষ দিক থেকে গণনা করে — যা সরাসরি আপনার ম্যানেজারের প্রশ্নের উত্তর দেয়:
print(orders.iloc[-3:])order_id date branch product category quantity price
5 1006 2024-03-03 south bag accessories 1 850.0
6 1007 2024-03-04 east pen stationery 20 15.0
7 1008 2024-03-04 north bottle accessories 4 120.0"শেষ তিনটি" হলো অবস্থান সম্পর্কিত একটি প্রশ্ন, তাই এর সাথে মানানসই টুল হলো .iloc। "শেষ" বলতে কী বোঝায় সে সম্পর্কে .loc-এর কোনো ধারণাই নেই; সে শুধু নাম চেনে।
লেবেল আর অবস্থান এক জিনিস নয়
এতক্ষণ পর্যন্ত লেবেল ২ এবং অবস্থান ২ একই সারি ছিল, তাই ফলাফল দেখে আপনি দুটি টুলের পার্থক্য ধরতে পারতেন না। শেষের তিনটি অর্ডারকে আলাদা টেবিল হিসেবে নিন, আর দেখুন কীভাবে তারা আলাদা হয়ে যায়:
last3 = orders.tail(3)
print(last3)order_id date branch product category quantity price
5 1006 2024-03-03 south bag accessories 1 850.0
6 1007 2024-03-04 east pen stationery 20 15.0
7 1008 2024-03-04 north bottle accessories 4 120.0সারিগুলো নিজেদের লেবেল ধরে রেখেছে: ৫, ৬, ৭। পান্ডাস ইচ্ছে করেই এদের নতুন করে নম্বর দেয়নি — টেবিল কাটার পরেও একটি সারি কীভাবে আগের সারি হিসেবেই চেনা যাবে, তা নিশ্চিত করে এই লেবেল। এবার উভয় উপায়ে "প্রথম সারিটি" চান:
print(last3.iloc[0])order_id 1006
date 2024-03-03
branch south
product bag
category accessories
quantity 1
price 850.0
Name: 5, dtype: objectlast3.loc[0]KeyError: 0.iloc[0] মানে "প্রথম স্থানের সারি", যা হলো ৫ লেবেলযুক্ত অর্ডার। আর .loc[0] মানে "যে সারির লেবেল ০" — কিন্তু এই টেবিলে এমন কোনো সারি নেই, তাই পান্ডাস KeyError দেয়। কোর্সের পরবর্তী অংশে এই ভুলটিই বড় সমস্যা তৈরি করে: যখন আপনি সারি ফিল্টার করেন বা সর্ট করেন — এ দুটিই কোর্সে পরে আসছে — অবস্থান বদলে যায় কিন্তু লেবেল বদলায় না। ভবিষ্যতের জন্য মনে রাখার নিয়ম:
- যদি স্থান বা অবস্থান নিয়ে প্রশ্ন হয় — প্রথম, শেষ, সর্ট করার পর শীর্ষ তিনটি?
.ilocব্যবহার করুন। - যদি পরিচয় বা সত্তা নিয়ে প্রশ্ন হয় — এই নির্দিষ্ট অর্ডার, এই পণ্য?
.locব্যবহার করুন।
আর সবচেয়ে খারাপ পরিস্থিতি কিন্তু KeyError নয়। সবচেয়ে খারাপ পরিস্থিতি হলো যখন টেবিলের অন্য কোথাও লেবেল ০ বিদ্যমান থাকে: তখন .loc[0] আপনার কাঙ্ক্ষিত সারির বদলে নীরবে সম্পূর্ণ ভিন্ন একটি সারি এনে দেয়।
.locচায় একটি নাম, আর.ilocচায় একটি স্থান। সদ্য পড়া কোনো টেবিলে কাকতালীয়ভাবে এ দুটি মিলে যায়, যার ফলে তাদের গুলিয়ে ফেলা কোড প্রথম দিন সব পরীক্ষায় পাস করে যায় — আর টেবিলটি যখনই কাটা, ফিল্টার বা সর্ট করা হয়, তখন কোনো ত্রুটি ছাড়াই ভুল সারি ফেরত দিতে শুরু করে।
সারিগুলোকে অর্থপূর্ণ লেবেল দেওয়া: set_index
হিসাবরক্ষক জানতে চেয়েছিলেন order 1006 সম্পর্কে। ডিফল্ট ইনডেক্স থাকলে আপনাকে জানতে হতো যে order 1006 রয়েছে ৫ নম্বর লেবেলের নিচে — যা ফাইলটির একটি কাকতালীয় তথ্য, অর্ডারের নিজস্ব বৈশিষ্ট্য নয়। একটি সারির আসল পরিচয় হলো তার অর্ডার নম্বর, তাই অর্ডার নম্বরটিকে ইনডেক্স বানিয়ে নিন:
by_id = orders.set_index("order_id")
print(by_id.head(3))
print(by_id.shape)date branch product category quantity price
order_id
1001 2024-03-01 north pen stationery 12 15.0
1002 2024-03-01 south notebook stationery 5 60.0
1003 2024-03-02 north bag accessories 2 850.0
(8, 6)তিনটি জিনিস পরিবর্তিত হয়েছে। অর্ডার নম্বরগুলো বাঁ দিকে ইনডেক্সে চলে গেছে। ইনডেক্সের এখন একটি নাম হয়েছে, order_id, যা তার উপরে নিজস্ব লাইনে প্রিন্ট হয়েছে। আর আকারটি (8, 7)-এর বদলে হয়েছে (8, 6) — order_id এখন আর কলাম নয়, এটি এখন ইনডেক্স। এটাও খেয়াল করুন যে set_index একটি নতুন টেবিল ফেরত দিয়েছে, যার নাম আমরা দিয়েছি by_id; মূল orders আগের মতোই অপরিবর্তিত আছে।
এখন .loc প্রশ্নের ভাষায় কথা বলে:
print(by_id.loc[1006])date 2024-03-03
branch south
product bag
category accessories
quantity 1
price 850.0
Name: 1006, dtype: objectআর একটি লেবেল স্লাইস এখন অর্ডার নম্বরের একটি রেঞ্জ, যার উভয় প্রান্তই অন্তর্ভুক্ত:
print(by_id.loc[1003:1005, ["product", "quantity"]])product quantity
order_id
1003 bag 2
1004 bottle 7
1005 eraser 30.iloc-এ কিন্তু কোনো পরিবর্তন হয়নি — লেবেল যাই হোক না কেন, অবস্থান অবস্থানই থাকে:
print(by_id.iloc[0:2])date branch product category quantity price
order_id
1001 2024-03-01 north pen stationery 12 15.0
1002 2024-03-01 south notebook stationery 5 60.0এখানে টুল দুটি স্পষ্টভাবে আলাদা হয়ে গেছে: by_id.loc[0] এখন একটি KeyError দেবে, কারণ ০ কোনো অর্ডার নম্বর নয়, যেখানে by_id.iloc[0] হলো অর্ডার ১০০১। আপনার যদি কখনো অর্ডার নম্বরটিকে আবার কলাম হিসেবে ফিরিয়ে আনার দরকার হয়, তবে by_id.reset_index() এটিকে আবার কলামে ফিরিয়ে দেয়।
এমন একটি কলাম বেছে নিন যার মানগুলো ইউনিক (অনন্য)। ইনডেক্সের উদ্দেশ্য হলো সারিকে সুনির্দিষ্টভাবে চিহ্নিত করা, আর দুটি সারির একই নাম থাকলে তা দিয়ে নির্দিষ্ট একটিকে নির্দেশ করা যায় না। product-এর ক্ষেত্রে কী ঘটে দেখুন, যা একাধিকবার এসেছে:
by_product = orders.set_index("product")
print(by_product.index.is_unique)
print(by_product.loc["bag"])False
order_id date branch category quantity price
product
bag 1003 2024-03-02 north accessories 2 850.0
bag 1006 2024-03-03 south accessories 1 850.0কোনো এরর নেই — কিন্তু loc["bag"] একটি সারিকে Series হিসেবে দেওয়ার বদলে দুই সারির একটি DataFrame ফেরত দিয়েছে। যে কোড একটিমাত্র সারির প্রত্যাশা করছিল (যেমন তার price একটি সংখ্যা হিসেবে পড়া), সে এখন দুটি সারি পাবে। এই কারণেই যখনই আপনি সারি খুঁজে বের করার উদ্দেশ্যে ইনডেক্স সেট করবেন, তখনই index.is_unique দিয়ে এক লাইনে যাচাই করে নেওয়া উচিত।
সারি এবং কলাম একসাথে
.loc এবং .iloc দুটোই কমা দিয়ে আলাদা করা দুটি অংশ নেয়: প্রথমে সারি, দ্বিতীয়তে কলাম। প্রতিটি অংশ একটি একক লেবেল, একটি তালিকা, বা একটি স্লাইস হতে পারে:
print(orders.loc[2:4, ["product", "price"]])product price
2 bag 850.0
3 bottle 120.0
4 eraser 8.0print(orders.loc[[0, 3], ["branch", "product"]])branch product
0 north pen
3 east bottleকলামের ক্ষেত্রেও স্লাইস কাজ করে, .loc-এর প্রান্ত অন্তর্ভুক্ত করার নিয়ম মেনে — "branch থেকে category পর্যন্ত প্রতিটি কলাম"। শুধু একটি কোলন : মানে হলো "সব সারি":
print(orders.loc[:, "branch":"category"])branch product category
0 north pen stationery
1 south notebook stationery
2 north bag accessories
3 east bottle accessories
4 north eraser stationery
5 south bag accessories
6 east pen stationery
7 north bottle accessories.iloc অবস্থান দিয়ে একই কাজ করে। কলামগুলোও ০ থেকে গোনা হয়, তাই branch হলো ২ নম্বর কলাম এবং product হলো ৩ নম্বর কলাম:
print(orders.iloc[[0, 3], [2, 3]])branch product
0 north pen
3 east bottleউপরের দুটি ব্লক আগের .loc সংস্করণের মতোই একই ফলাফল — কিন্তু লক্ষ্য করুন এটি পড়া কতটা কঠিন। [2, 3] কিছুই বোঝায় না; কিন্তু ["branch", "product"] সবকিছু পরিষ্কার বলে দেয়, এমনকি ফাইলের শুরুতে কেউ নতুন একটি কলাম যোগ করলেও কোড ঠিকঠাক কাজ করে। তাই নাম জানা থাকলে নাম ব্যবহার করাই শ্রেয়।
কী আকারের ফলাফল ফেরত আসবে?
আপনি একটি একক মান, একটি Series নাকি একটি DataFrame পাবেন, তা নির্ভর করে কমার উভয় পাশে আপনি কী দিয়েছেন তার ওপর। একটি একক লেবেল সেই মাত্রাটি (dimension) বাদ দেয়; একটি তালিকা বা স্লাইস সেই মাত্রাটি বজায় রাখে:
print(type(by_id.loc[1006, "price"]).__name__)
print(type(by_id.loc[1006, ["product", "price"]]).__name__)
print(type(by_id.loc[[1003, 1006], "price"]).__name__)
print(type(by_id.loc[[1003, 1006], ["product", "price"]]).__name__)float64
Series
Series
DataFrame- একটি লেবেল, একটি লেবেল → একটি একক মান
- একটি লেবেল, একটি তালিকা বা স্লাইস → একটি
Series(একটি সারি) - একটি তালিকা বা স্লাইস, একটি লেবেল → একটি
Series(একটি কলাম) - একটি তালিকা বা স্লাইস, একটি তালিকা বা স্লাইস → একটি
DataFrame
এটি হলো পরিকল্পনার ৩ নম্বর ধাপ — উত্তরের আকার কল্পনা করা — যা একটি নিয়মে পরিণত হয়েছে। আপনি যদি এক সারির হলেও একটি টেবিল ফেরত পেতে চান, তবে উভয় পাশে তালিকা দিন: by_id.loc[[1006], ["price"]]।
একটি একক মান: .loc এবং .at
হিসাবরক্ষকের প্রশ্ন — order 1006-এর দাম কত ছিল? — এটি একটিমাত্র ঘর: একটি সারির লেবেল এবং একটি কলামের নাম।
print(by_id.loc[1006, "price"])
print(by_id.at[1006, "price"])850.0
850.0দুটোই একই সংখ্যা দেয়। .at শুধুমাত্র একটি একক সারি এবং একটি একক কলাম গ্রহণ করে, অন্য কিছু নয়, যা একে কিছুটা দ্রুতগতির করে এবং আপনার উদ্দেশ্য স্পষ্ট করে: এটি একটিমাত্র ঘর। (অবস্থানভিত্তিক যমজ হলো .iat, যেমন by_id.iat[5, 5]।) যেটি পড়তে ভালো লাগে সেটি ব্যবহার করুন; .loc-ও চমৎকার।
একটি একক মান একটি সাধারণ সংখ্যা, তাই এটি দিয়ে আপনি সরাসরি হিসাবনিকাশ করতে পারেন। অর্ডার ১০০৪-এর মোট মূল্য — পরিমাণ গুণ দাম:
print(by_id.at[1004, "quantity"] * by_id.at[1004, "price"])840.0সিলেকশন একটি আলাদা অবজেক্ট
বাস্তব প্রোগ্রামে সিলেকশন ব্যবহার করার আগে আরও একটি বিষয় বোঝা দরকার: যখন আপনি কোনো সিলেকশনকে পরিবর্তন করেন তখন কী ঘটে।
import pandas as pd
orders = pd.read_csv("orders.csv")
prices = orders["price"]
prices[0] = 999.0
print(prices.head(2))
print(orders.loc[0, "price"])0 999.0
1 60.0
Name: price, dtype: float64
15.0prices পরিবর্তিত হয়েছে। orders হয়নি। পান্ডাস ৩-এ প্রতিটি সিলেকশন একটি পৃথক কপি (অনুলিপি) হিসেবে আচরণ করে: আপনি নির্দ্বিধায় এটি পরিবর্তন করতে পারেন এবং মূল টেবিল কখনোই স্পর্শিত হয় না। (অভ্যন্তরীণভাবে পান্ডাস যতক্ষণ না আপনি নতুন মান লিখছেন ততক্ষণ পর্যন্ত ডেটা কপি করা এড়িয়ে চলে — এই ব্যবস্থার নাম কপি-অন-রাইট (Copy-on-Write) — তবে আপনার যে নিয়মটি দরকার তা হলো এর বাহ্যিক আচরণ, অভ্যন্তরীণ মেকানিজম নয়।)
একাধিক কলামের সিলেকশনের ক্ষেত্রেও একই কথা প্রযোজ্য:
stock = orders[["product", "price"]]
stock["price"] = 0
print(orders["price"].head(2))0 15.0
1 60.0
Name: price, dtype: float64এটি একটি নিরাপদ আচরণ: একটি ফাংশন যা কোনো সিলেকশন নিয়ে তা সম্পাদনা করে, তা আপনার অজান্তে মূল টেবিল নষ্ট করতে পারে না। তবে এর একটি পরিণতি রয়েছে যা আপনার জানা আবশ্যক। আপনি যদি সত্যিই মূল টেবিলটি পরিবর্তন করতে চান, তবে আপনাকে মূল টেবিলেই .loc দিয়ে এক ধাপে তা করতে হবে:
orders.loc[0, "price"] = 16.0
print(orders.loc[0, "price"])16.0প্রলুব্ধকর দুই ধাপের সংস্করণটি — প্রথমে কলাম বেছে নেওয়া, তারপর তার একটি ঘরে মান বসানো — কাজ করে না, এবং পান্ডাস ৩ নিজেই আপনাকে তা জানিয়ে দেয়:
orders["price"][0] = 1.0ChainedAssignmentError: A value is being set on a copy of a DataFrame or Series through chained assignment.
Such chained assignment never works to update the original DataFrame or Series, because the intermediate object on which we are setting values always behaves as a copy (due to Copy-on-Write).
Try using '.loc[row_indexer, col_indexer] = value' instead, to perform the assignment in a single step.
See the documentation for a more detailed explanation: https://pandas.pydata.org/pandas-docs/stable/user_guide/copy_on_write.html#chained-assignmentprint(orders.loc[0, "price"])16.0এখনও 16.0 — অ্যাসাইনমেন্টটি একটি অস্থায়ী কপিতে চলে গিয়েছিল এবং ফেলে দেওয়া হয়েছে। orders["price"] একটি সিলেকশন তৈরি করেছিল, এবং [0] = 1.0 সেটাকে পরিবর্তন করেছে, orders-কে নয়। একে বলে চেইন্ড অ্যাসাইনমেন্ট (chained assignment): = চিহ্নের বাঁ দিকে পরপর দুটি ব্র্যাকেট। এর সমাধান সবসময় সেই একটাই যা সতর্কবার্তায় বলা হয়েছে: সারি ও কলাম উভয় সম্বলিত একটি একক .loc, যেমন orders.loc[0, "price"] = 1.0।
পুরোনো টিউটোরিয়াল পড়লে আপনি সেখানে SettingWithCopyWarning এবং কখনো মূল টেবিল বদলায় কখনো বদলায় না এমন পরামর্শ দেখতে পাবেন। সেটা ছিল পান্ডাস ১ এবং ২-এর ক্ষেত্রে। পান্ডাস ৩-এ নিয়মটি সহজ এবং সবসময় একই: সিলেকশন কখনোই মূল টেবিল পরিবর্তন করে না; মূল টেবিলে .loc ব্যবহার করলে তা পরিবর্তন হয়।
একটি সম্পূর্ণ উদাহরণ
অধ্যায়ের শুরুর তিনটি অনুরোধের উত্তর একটিমাত্র প্রোগ্রামে দেওয়া হলো। select_orders.py:
import pandas as pd
orders = pd.read_csv("orders.csv")
# 1. Check what arrived before pointing into it
print("Shape :", orders.shape)
print("Columns:", list(orders.columns))
# 2. Name the rows by what they are
by_id = orders.set_index("order_id")
print("Order ids unique:", by_id.index.is_unique)
print()
# Stock team: two columns, every row
stock = orders[["product", "quantity"]]
print("Stock view:", stock.shape)
print(stock.head(3))
print()
# Accountant: one order by its number, then one cell
print(by_id.loc[1006, ["product", "quantity", "price"]])
print("Order 1006 total:", by_id.at[1006, "quantity"] * by_id.at[1006, "price"])
print()
# Manager: the last three orders, a question about position
print(by_id.iloc[-3:, [1, 2, 4]])Shape : (8, 7)
Columns: ['order_id', 'date', 'branch', 'product', 'category', 'quantity', 'price']
Order ids unique: True
Stock view: (8, 2)
product quantity
0 pen 12
1 notebook 5
2 bag 2
product bag
quantity 1
price 850.0
Name: 1006, dtype: object
Order 1006 total: 850.0
branch product quantity
order_id
1006 south bag 1
1007 east pen 20
1008 north bottle 4কোডটি কেন এভাবে লেখা হয়েছে:
- নির্দেশ করার আগেই দেখে নেওয়া হয়েছে।
shapeএবংlist(columns)আগেই প্রিন্ট করা হয়েছে, যাতে কোনো কলামের নামে বানান ভুল বা স্পেস থাকলে কোনো সিলেকশন ব্যর্থ হওয়ার আগেই তা ধরা পড়ে। - ইনডেক্স ইউনিক কি না তা যাচাই করা হয়েছে অর্ডার খুঁজে নেওয়ার জন্য ব্যবহার করার আগে, যাতে
by_id.loc[1006]নিশ্চিতভাবে একটি সারি এবংby_id.at[...]একটি সংখ্যা হয়। - প্রতিটি অনুরোধের জন্য সেই টুল ব্যবহার করা হয়েছে যা প্রশ্নের সাথে মানানসই। নাম দিয়ে কলামের জন্য ব্র্যাকেট; অর্ডারের পরিচয় দিয়ে খুঁজতে
.locএবং.at; আর অবস্থান দিয়ে "শেষ তিনটি" বের করতে.iloc। - এটি স্টক ভিউয়ের shape প্রিন্ট করেছে —
(8, 2)ঠিক ভাবনার সাথে মিলে গেছে: প্রতিটি অর্ডার, দুটি কলাম। - শেষ সিলেকশনটি উন্নত করার মতো একটি জায়গা।
by_id-তে[1, 2, 4]দ্বারা বোঝাচ্ছেbranch,product,quantity— লক্ষ্য করুনorder_idইনডেক্স হওয়ায় অবস্থানগুলো এক ঘর সরে গেছে। অবস্থান ভঙ্গুর। নিজের কোডেby_id.iloc[-3:][["branch", "product", "quantity"]]বাby_id.loc[by_id.index[-3:], ["branch", "product", "quantity"]]নাম ব্যবহারের মাধ্যমে একই কথা আরও স্পষ্টভাবে বলে দেয়।
কিছু ভাঙা অবস্থা ও তার সমাধান
KeyError: 'Price' ঠিক ওই নামের কোনো কলাম নেই। কলামের নাম কেস-সেনসিটিভ: price এবং Price দুটি আলাদা নাম। list(df.columns) প্রিন্ট করুন এবং সেখান থেকে নামটি হুবহু কপি করুন। নামের তালিকা দিলে, যেমন orders[["product", "Price"]], মেসেজটি হয় KeyError: "['Price'] not in index" এবং কোনটি অনুপস্থিত তা স্পষ্টভাবে দেখিয়ে দেয়; ডট দিলে, যেমন orders.Price, এটি হয় AttributeError: 'DataFrame' object has no attribute 'Price'। একই কারণ, একই সমাধান।
KeyError: 'price' — অথচ ফাইলে স্পষ্ট দেখা যাচ্ছে price আছে সম্ভবত হেডারে কমার পরে স্পেস রয়েছে, যেমন order_id, product, price, এবং সেই স্পেসগুলো নামের অংশ হয়ে গেছে:
from io import StringIO
import pandas as pd
raw = "order_id, product, price\n1001, pen, 15.0\n"
padded = pd.read_csv(StringIO(raw))
print(list(padded.columns))
padded["price"]['order_id', ' product', ' price']
KeyError: 'price'তালিকায় স্পষ্ট দেখা যাচ্ছে ' price'-এর শুরুতে স্পেস রয়েছে — আর ঠিক এই কারণেই অধ্যায় তিন আপনাকে কলামগুলোকে তালিকা হিসেবে প্রিন্ট করতে বলেছিল। ফাইল পড়ার সময়ই এটি ঠিক করুন pd.read_csv("file.csv", skipinitialspace=True) দিয়ে, অথবা পরে df.columns = df.columns.str.strip() দিয়ে।
KeyError: ('product', 'price') আপনি orders["product", "price"] লিখেছেন — এক জোড়া ব্র্যাকেট দিয়ে। ভেতরের তালিকাটি ছাড়া, পাইথন নাম দুটিকে একটি একক টাপল হিসেবে পাঠায়, আর পান্ডাস ('product', 'price') নামের একটি কলাম খুঁজতে থাকে। লিখুন orders[["product", "price"]]।
.loc থেকে KeyError: 0 আপনি .loc-এ অবস্থান ব্যবহার করেছেন। হয় টেবিলটি কেটে ছোট করা হয়েছিল (tail, ফিল্টার বা সর্ট) যার ফলে লেবেল ০ হারিয়ে গেছে, অথবা আপনি ইনডেক্স সেট করেছেন এবং লেবেলগুলো এখন অর্ডার নম্বর হয়ে গেছে। আপনি যদি "প্রথম সারি" বোঝাতে চান, তবে লিখুন .iloc[0]। যে টেবিলের ইনডেক্স টেক্সট, সেখানে স্লাইস নিয়ে একই ভুল করলে — যেমন orders.set_index("product").loc[0:2] — পরিবর্তে TypeError: cannot do slice indexing on Index with these indexers [0] of type int দেখা দেয়; এর সমাধানও একই, .iloc[0:2]।
IndexError: single positional indexer is out-of-bounds .iloc-এ এমন একটি অবস্থান দেওয়া হয়েছে যার কোনো অস্তিত্ব নেই — আট সারির টেবিলে orders.iloc[8], যার অবস্থান ০ থেকে ৭ পর্যন্ত। df.shape দেখুন; শেষ সারিটি সবসময় df.iloc[-1]। কলামের অবস্থান মাত্রাতিরিক্ত বড় হলেও একই বার্তা আসে, যেমন orders.iloc[:, 7]।
ValueError: Location based indexing can only have [integer, integer slice (START point is INCLUDED, END point is EXCLUDED), listlike of integers, boolean array] types আপনি .iloc-এ একটি কলামের নাম দিয়েছেন: orders.iloc[0, "branch"]। .iloc শুধুমাত্র অবস্থান গ্রহণ করে। লেবেল দিয়ে .loc ব্যবহার করুন — orders.loc[0, "branch"] — অথবা যদি সত্যিই অবস্থানের প্রয়োজন হয়, তবে তা বের করে নিন: orders.columns.get_loc("branch") দেয় 2।
ধাপ ৪ / ৬ — অনুমান
যাচাই করুন
একই টেবিল, ব্র্যাকেটের ভেতরে একই সংখ্যা। কী প্রিন্ট হবে?
from io import StringIO
import pandas as pd
# Stands in for orders.csv, so this snippet runs on its own.
RAW = """order_id,date,branch,product,category,quantity,price
1001,2024-03-01,north,pen,stationery,12,15.0
1002,2024-03-01,south,notebook,stationery,5,60.0
1003,2024-03-02,north,bag,accessories,2,850.0
1004,2024-03-02,east,bottle,accessories,7,120.0
1005,2024-03-03,north,eraser,stationery,30,8.0
1006,2024-03-03,south,bag,accessories,1,850.0
1007,2024-03-04,east,pen,stationery,20,15.0
1008,2024-03-04,north,bottle,accessories,4,120.0
"""
orders = pd.read_csv(StringIO(RAW))
print(len(orders.loc[2:5]), len(orders.iloc[2:5]))- A4 4
- B3 3
- C4 3
- D3 4
এর উদ্দেশ্য ছিল শেষের তিনটি অর্ডারের প্রথমটির পণ্য প্রিন্ট করা। বাস্তবে কী ঘটবে, এবং কেন?
from io import StringIO
import pandas as pd
# Stands in for orders.csv, so this snippet runs on its own.
RAW = """order_id,date,branch,product,category,quantity,price
1001,2024-03-01,north,pen,stationery,12,15.0
1002,2024-03-01,south,notebook,stationery,5,60.0
1003,2024-03-02,north,bag,accessories,2,850.0
1004,2024-03-02,east,bottle,accessories,7,120.0
1005,2024-03-03,north,eraser,stationery,30,8.0
1006,2024-03-03,south,bag,accessories,1,850.0
1007,2024-03-04,east,pen,stationery,20,15.0
1008,2024-03-04,north,bottle,accessories,4,120.0
"""
orders = pd.read_csv(StringIO(RAW))
last3 = orders.tail(3)
# Wanted: the product of the first of these three orders
print(last3.loc[0, "product"])- Aএটি `pen` প্রিন্ট করবে, যা মূল টেবিলের প্রথম সারির পণ্য
- Bএটি `bag` প্রিন্ট করবে, যা ওই তিনটি সারির প্রথমটির পণ্য
- C`KeyError: 0` — `tail(3)` লেবেল ৫, ৬ এবং ৭ ধরে রেখেছে, আর `.loc` খুঁজছে লেবেল ০
- D`IndexError` — টেবিলটিতে মাত্র তিনটি সারি রয়েছে
আপনার শুধুমাত্র price কলাম সম্বলিত একটি DataFrame দরকার — কোনো Series নয়। কোন লাইনটি আপনাকে তা দেবে?
- Aorders["price"]
- Borders.price
- Corders.loc[:, "price"]
- Dorders[["price"]]
উত্তর দিতে অ্যাকাউন্ট লাগবে
উত্তর মিলিয়ে দেখতে সাইন ইন করুন
প্রশ্নগুলো উপরে আছে, আর মাথায় মাথায় উত্তর ভেবে নেওয়াই আসল কাজ। সঠিক উত্তর, ব্যাখ্যা আর তিন ধাপের ইঙ্গিত দেখতে সাইন ইন করুন।
নিজে করুন
একই orders.csv ব্যবহার করে select.py লিখুন। কোনো কোড লেখার আগে, প্রতিটি সিলেকশন কী ফেরত দেবে — একটি একক মান, একটি Series নাকি একটি DataFrame — এবং কয়টি সারি, তা কমেন্টে লিখুন। প্রোগ্রামে চারটি শর্ত পূরণ হতে হবে:
- কলামগুলো সবসময় নাম দিয়ে বেছে নিতে হবে:
date,branchএবংquantityএকসাথে নিয়ে তাদেরshapeপ্রিন্ট করুন। - ইনডেক্স ইউনিক কি না যাচাই করার পর,
order_idদিয়ে ইনডেক্স করা টেবিলে পরিচয় দিয়ে অর্ডারগুলো খুঁজুন: ১০০৭ নম্বর অর্ডারের পুরো সারিটি প্রিন্ট করুন, তারপর ১০০২ থেকে ১০০৫ নম্বর অর্ডারেরproductএবংbranchপ্রিন্ট করুন — এই চারটিই। - "প্রথম" এবং "শেষ" বের করতে হবে অবস্থান দিয়ে: প্রথম দুটি এবং শেষ দুটি অর্ডারের
productওpriceপ্রিন্ট করুন; তারপর.atদিয়ে ১০০৫ নম্বর অর্ডারের পরিমাণ এবং ১০০৩ নম্বর অর্ডারের মোট মূল্য (পরিমাণ × দাম) প্রিন্ট করুন। - ইনডেক্স করা টেবিলে একটি একক
.locদিয়ে ১০০১ নম্বর অর্ডারের দাম বদলে16.0করুন, এবং পরিবর্তনটি যে ঘটেছে তা প্রমাণ করতে মানটি প্রিন্ট করুন।
আপনার আউটপুট অবিকল এরকম হওয়া উচিত:
(8, 3)
unique: True
date 2024-03-04
branch east
product pen
category stationery
quantity 20
price 15.0
Name: 1007, dtype: object
product branch
order_id
1002 notebook south
1003 bag north
1004 bottle east
1005 eraser north
product price
order_id
1001 pen 15.0
1002 notebook 60.0
product price
order_id
1007 pen 15.0
1008 bottle 120.0
qty of 1005: 30
total of 1003: 1700.0
price of 1001: 16.0তারপর ফলাফল কী হবে অনুমান করুন, আপনার অনুমানটি লিখে রাখুন, এবং কেবল তখনই এই তিনটি লাইনের প্রতিটি চালান:
by_id.loc[0]orders.iloc[8]by_id["price"][1001] = 99.0— এবং পরে আবারও ১০০১ নম্বর অর্ডারের দাম প্রিন্ট করুন
সমাধান
আগে পরিকল্পনা করুন। কমেন্ট ব্লকটি হলো "কোড লেখার আগে" অংশের ৩ নম্বর ধাপের খসড়া — প্রতিটি সিলেকশন কী ফেরত দেবে:
- তিনটি কলাম →
orders[[...]]→(8, 3)আকারের একটিDataFrame - একটি অর্ডার, পরিচয় দিয়ে →
by_id.loc[1007]→ একটিSeries(একটি সারি) - পরিচয় দিয়ে চারটি অর্ডার, দুটি কলাম →
by_id.loc[1002:1005, [...]]→ একটি ৪ × ২DataFrame, কারণ.loc১০০৫-কেও অন্তর্ভুক্ত করে - অবস্থান দিয়ে প্রথম দুটি ও শেষ দুটি →
.iloc[:2]এবং.iloc[-2:]→ দুটি ২ × ২DataFrame - একক ঘর →
.at→ সাধারণ সংখ্যা - মূল টেবিল পরিবর্তন করা →
by_id-তে একটি একক.loc→ টেবিলটি নিজেই পরিবর্তিত হয়
select.py:
import pandas as pd
orders = pd.read_csv("orders.csv")
# Columns by name -> a DataFrame of 8 rows, 3 columns
print(orders[["date", "branch", "quantity"]].shape)
# Rows named by their order number; check the names are unique first
by_id = orders.set_index("order_id")
print("unique:", by_id.index.is_unique)
# One order by identity -> a Series; four orders -> 4 x 2 (loc includes 1005)
print(by_id.loc[1007])
print(by_id.loc[1002:1005, ["product", "branch"]])
# First two and last two by place -> .iloc for rows, names for columns
print(by_id.iloc[:2][["product", "price"]])
print(by_id.iloc[-2:][["product", "price"]])
# Single cells -> plain numbers
print("qty of 1005:", by_id.at[1005, "quantity"])
print("total of 1003:", by_id.at[1003, "quantity"] * by_id.at[1003, "price"])
# Change the original, in one step
by_id.loc[1001, "price"] = 16.0
print("price of 1001:", by_id.at[1001, "price"])(8, 3)
unique: True
date 2024-03-04
branch east
product pen
category stationery
quantity 20
price 15.0
Name: 1007, dtype: object
product branch
order_id
1002 notebook south
1003 bag north
1004 bottle east
1005 eraser north
product price
order_id
1001 pen 15.0
1002 notebook 60.0
product price
order_id
1007 pen 15.0
1008 bottle 120.0
qty of 1005: 30
total of 1003: 1700.0
price of 1001: 16.0প্রতিটি ফলাফল পরিকল্পনার সাথে মিলে গেছে: তিনটি কলামের জন্য (8, 3), 1002:1005-এর জন্য চারটি অর্ডার কারণ .loc তার শেষ সীমা অন্তর্ভুক্ত করে, এবং অবস্থানভিত্তিক সিলেকশনগুলোর প্রতিটিতে দুটি করে সারি। ৩ নম্বর শর্তে সারিগুলো এসেছে অবস্থান দিয়ে এবং কলামগুলো নাম দিয়ে — সারির জন্য .iloc, তারপর নামের একটি তালিকা — তাই কোডটি তার প্রকৃত অর্থ প্রকাশ করে।
এবার তিনটি অনুমানের ফলাফল:
by_id.loc[0]KeyError: 0লেবেলগুলো এখন অর্ডার নম্বর; ০ নম্বরের কোনো অর্ডার নেই। অবস্থান দিয়ে প্রথম সারি হতো by_id.iloc[0]।
orders.iloc[8]IndexError: single positional indexer is out-of-boundsআটটি সারি মানে অবস্থান ০ থেকে ৭ পর্যন্ত। শেষ সারিটি হলো orders.iloc[7], অথবা আরও ভালো, orders.iloc[-1]।
by_id["price"][1001] = 99.0ChainedAssignmentError: A value is being set on a copy of a DataFrame or Series through chained assignment.
Such chained assignment never works to update the original DataFrame or Series, because the intermediate object on which we are setting values always behaves as a copy (due to Copy-on-Write).
Try using '.loc[row_indexer, col_indexer] = value' instead, to perform the assignment in a single step.
See the documentation for a more detailed explanation: https://pandas.pydata.org/pandas-docs/stable/user_guide/copy_on_write.html#chained-assignmentprint(by_id.at[1001, "price"])16.0এখনও 16.0: by_id["price"] একটি আলাদা সিলেকশন তৈরি করেছিল এবং 99.0 সেটিতে চলে গেছে, by_id-তে নয়। শর্ত ৪ সফল হয়েছিল কিন্তু এটি হয়নি, আর পার্থক্য হলো একটি একক .loc বনাম পরপর দুটি ব্র্যাকেট।
ধাপ ৬ / ৬
কঠিন করা — অধ্যায়ের কুইজ
সহজ থেকে কঠিন — দশটি প্রশ্ন, শেষেরগুলো ইচ্ছে করেই কঠিন।
সাইন ইন করে কুইজ দিন