BigQuery DataFrames প্যাকেজ ব্যবহার করে আইওয়া মদের বিক্রয়ের অনুসন্ধানমূলক ডেটা বিশ্লেষণ

1. সংক্ষিপ্ত বিবরণ

এই ল্যাবে, আপনি আইওয়া অ্যালকোহল বিক্রয় পাবলিক ডেটাসেট পরিষ্কার এবং বিশ্লেষণ করতে BigQuery স্টুডিওতে একটি পাইথন নোটবুক থেকে BigQuery DataFrames ব্যবহার করবেন। অন্তর্দৃষ্টি আবিষ্কার করতে BigQuery ML এবং রিমোট ফাংশন ক্ষমতা ব্যবহার করুন।

ভৌগোলিক অঞ্চল জুড়ে বিক্রয় তুলনা করার জন্য আপনি একটি পাইথন নোটবুক তৈরি করবেন। এটি যেকোনো কাঠামোগত ডেটার উপর কাজ করার জন্য অভিযোজিত করা যেতে পারে।

উদ্দেশ্য

এই ল্যাবে, আপনি নিম্নলিখিত কাজগুলি কীভাবে সম্পাদন করবেন তা শিখবেন:

  • BigQuery Studio-তে Python নোটবুক সক্রিয় করুন এবং ব্যবহার করুন
  • BigQuery DataFrames প্যাকেজ ব্যবহার করে BigQuery-এর সাথে সংযোগ করুন
  • BigQuery ML ব্যবহার করে একটি লিনিয়ার রিগ্রেশন তৈরি করুন
  • একটি পরিচিত পান্ডার মতো সিনট্যাক্স ব্যবহার করে জটিল সমষ্টি এবং যোগদান সম্পাদন করুন

2. প্রয়োজনীয়তা

শুরু করার আগে

এই কোডল্যাবের নির্দেশাবলী অনুসরণ করার জন্য, আপনার BigQuery Studio সক্ষম থাকা একটি Google Cloud Project এবং একটি সংযুক্ত বিলিং অ্যাকাউন্টের প্রয়োজন হবে।

  1. গুগল ক্লাউড কনসোলে , প্রজেক্ট সিলেক্টর পৃষ্ঠায়, একটি গুগল ক্লাউড প্রজেক্ট নির্বাচন করুন বা তৈরি করুন।
  2. আপনার গুগল ক্লাউড প্রোজেক্টের জন্য বিলিং সক্ষম করা আছে কিনা তা নিশ্চিত করুন। কোনও প্রোজেক্টে বিলিং সক্ষম করা আছে কিনা তা কীভাবে পরীক্ষা করবেন তা জানুন।
  3. সম্পদ ব্যবস্থাপনার জন্য BigQuery Studio সক্ষম করতে নির্দেশাবলী অনুসরণ করুন।

BigQuery স্টুডিও প্রস্তুত করুন

একটি খালি নোটবুক তৈরি করুন এবং এটি একটি রানটাইমের সাথে সংযুক্ত করুন।

  1. গুগল ক্লাউড কনসোলে BigQuery স্টুডিওতে যান।
  2. + বোতামের পাশে ক্লিক করুন।
  3. পাইথন নোটবুক নির্বাচন করুন।
  4. টেমপ্লেট নির্বাচকটি বন্ধ করুন।
  5. একটি নতুন কোড সেল তৈরি করতে + কোড নির্বাচন করুন।
  6. কোড সেল থেকে BigQuery DataFrames প্যাকেজের সর্বশেষ সংস্করণটি ইনস্টল করুন। নিম্নলিখিত কমান্ডটি টাইপ করুন।
    %pip install --upgrade bigframes --quiet
    
    কোড সেলটি রান করতে রান সেল বোতামে ক্লিক করুন অথবা Shift + Enter টিপুন।

৩. একটি পাবলিক ডেটাসেট পড়ুন

একটি নতুন কোড সেলে নিম্নলিখিতটি চালিয়ে BigQuery DataFrames প্যাকেজটি শুরু করুন:

import bigframes.pandas as bpd

bpd.options.bigquery.ordering_mode = "partial"
bpd.options.display.repr_mode = "deferred"

দ্রষ্টব্য: এই টিউটোরিয়ালে, আমরা পরীক্ষামূলক "আংশিক ক্রম মোড" ব্যবহার করেছি, যা পান্ডা-সদৃশ ফিল্টারিংয়ের সাথে ব্যবহার করলে আরও দক্ষ প্রশ্নের জন্য অনুমতি দেয়। কিছু পান্ডা বৈশিষ্ট্য যার জন্য কঠোর ক্রম বা সূচক প্রয়োজন তা কাজ নাও করতে পারে।

আপনার bigframes প্যাকেজ সংস্করণটি দেখুন

bpd.__version__

এই টিউটোরিয়ালটির জন্য ১.২৭.০ বা তার পরবর্তী সংস্করণ প্রয়োজন।

আইওয়া মদের খুচরা বিক্রয়

আইওয়া মদের খুচরা বিক্রয় ডেটাসেট গুগল ক্লাউডের পাবলিক ডেটাসেট প্রোগ্রামের মাধ্যমে BigQuery-তে সরবরাহ করা হয়েছে। এই ডেটাসেটে ১ জানুয়ারী, ২০১২ সাল থেকে খুচরা বিক্রেতাদের দ্বারা আইওয়া রাজ্যে ব্যক্তিদের কাছে বিক্রয়ের জন্য প্রতিটি পাইকারি মদের ক্রয় অন্তর্ভুক্ত রয়েছে। আইওয়া বাণিজ্য বিভাগের অন্তর্গত অ্যালকোহলিক পানীয় বিভাগ দ্বারা তথ্য সংগ্রহ করা হয়।

BigQuery-তে, আইওয়া মদের খুচরা বিক্রয় বিশ্লেষণ করতে bigquery-public-data.iowa_liquor_sales.sales অনুসন্ধান করুন। একটি কোয়েরি স্ট্রিং বা টেবিল আইডি থেকে একটি ডেটাফ্রেম তৈরি করতে bigframes.pandas.read_gbq() পদ্ধতি ব্যবহার করুন।

"df" নামের একটি ডেটাফ্রেম তৈরি করতে একটি নতুন কোড সেলে নিম্নলিখিতটি চালান:

df = bpd.read_gbq_table("bigquery-public-data.iowa_liquor_sales.sales")

একটি ডেটাফ্রেম সম্পর্কে প্রাথমিক তথ্য আবিষ্কার করুন

ডেটার একটি ছোট নমুনা ডাউনলোড করতে DataFrame.peek() পদ্ধতি ব্যবহার করুন।

এই সেলটি চালান:

df.peek()

প্রত্যাশিত আউটপুট:

index	invoice_and_item_number	date	store_number	store_name	...
0	RINV-04620300080	2023-04-28	10197	SUNSHINE FOODS / HAWARDEN	
1	RINV-04864800097	2023-09-25	2621	HY-VEE FOOD STORE #3 / SIOUX CITY	
2	RINV-05057200028	2023-12-28	4255	FAREWAY STORES #058 / ORANGE CITY	
3	...				

দ্রষ্টব্য: head() জন্য অর্ডারিং প্রয়োজন এবং আপনি যদি ডেটার একটি নমুনা কল্পনা করতে চান তবে এটি সাধারণত peek() এর চেয়ে কম কার্যকর।

ঠিক যেমন পান্ডাদের ক্ষেত্রে হয়, DataFrame.dtypes প্রোপার্টি ব্যবহার করে সমস্ত উপলব্ধ কলাম এবং তাদের সংশ্লিষ্ট ডেটা টাইপগুলি দেখুন। এগুলি পান্ডা-সামঞ্জস্যপূর্ণ উপায়ে প্রকাশ করা হয়।

এই সেলটি চালান:

df.dtypes

প্রত্যাশিত আউটপুট:

invoice_and_item_number	string[pyarrow]
date	date32[day][pyarrow]
store_number	string[pyarrow]
store_name	string[pyarrow]
address	string[pyarrow]
city	string[pyarrow]
zip_code	string[pyarrow]
store_location	geometry
county_number	string[pyarrow]
county	string[pyarrow]
category	string[pyarrow]
category_name	string[pyarrow]
vendor_number	string[pyarrow]
vendor_name	string[pyarrow]
item_number	string[pyarrow]
item_description	string[pyarrow]
pack	Int64
bottle_volume_ml	Int64
state_bottle_cost	Float64
state_bottle_retail	Float64
bottles_sold	Int64
sale_dollars	Float64
volume_sold_liters	Float64
volume_sold_gallons	Float64

dtype: object

DataFrame.describe() পদ্ধতি DataFrame থেকে কিছু মৌলিক পরিসংখ্যান অনুসন্ধান করে। এই সারাংশ পরিসংখ্যানগুলিকে pandas DataFrame হিসেবে ডাউনলোড করতে DataFrame.to_pandas() চালান।

এই সেলটি চালান:

df.describe("all").to_pandas()

প্রত্যাশিত আউটপুট:

	invoice_and_item_number	date	store_number	store_name	...
nunique	30305765	<NA>	3158	3353	...
std	<NA>	<NA>	<NA>	<NA>	...
mean	<NA>	<NA>	<NA>	<NA>	...
75%	<NA>	<NA>	<NA>	<NA>	...
25%	<NA>	<NA>	<NA>	<NA>	...
count	30305765	<NA>	30305765	30305765	...
min	<NA>	<NA>	<NA>	<NA>	...
50%	<NA>	<NA>	<NA>	<NA>	...
max	<NA>	<NA>	<NA>	<NA>	...
9 rows × 24 columns

৪. ডেটা ভিজ্যুয়ালাইজ করুন এবং পরিষ্কার করুন

আইওয়া মদের খুচরা বিক্রয় ডেটাসেট সূক্ষ্ম ভৌগোলিক তথ্য প্রদান করে, যার মধ্যে খুচরা দোকানগুলি কোথায় অবস্থিত তাও অন্তর্ভুক্ত। ভৌগোলিক অঞ্চল জুড়ে প্রবণতা এবং পার্থক্য সনাক্ত করতে এই তথ্যগুলি ব্যবহার করুন।

জিপ কোড প্রতি বিক্রয় কল্পনা করুন

DataFrame.plot.hist() এর মতো বেশ কিছু অন্তর্নির্মিত ভিজ্যুয়ালাইজেশন পদ্ধতি রয়েছে। জিপ কোড অনুসারে মদের বিক্রির তুলনা করতে এই পদ্ধতিটি ব্যবহার করুন।

volume_by_zip = df.groupby("zip_code").agg({"volume_sold_liters": "sum"})
volume_by_zip.plot.hist(bins=20)

প্রত্যাশিত আউটপুট:

আয়তনের হিস্টোগ্রাম

কোন জিপ কোল্ডে সবচেয়ে বেশি অ্যালকোহল বিক্রি হয়েছে তা দেখতে একটি বার চার্ট ব্যবহার করুন।

(
  volume_by_zip
  .sort_values("volume_sold_liters", ascending=False)
  .head(25)
  .to_pandas()
  .plot.bar(rot=80)
)

প্রত্যাশিত আউটপুট:

সর্বাধিক বিক্রিত জিপ কোডগুলিতে অ্যালকোহলের পরিমাণের বার চার্ট

ডেটা পরিষ্কার করুন

কিছু জিপ কোডের একটি ট্রেইলিং .0 থাকে। সম্ভবত ডেটা সংগ্রহের কোথাও জিপ কোডগুলি দুর্ঘটনাক্রমে ফ্লোটিং পয়েন্ট মানে রূপান্তরিত হয়েছে। জিপ কোডগুলি পরিষ্কার করতে এবং বিশ্লেষণ পুনরাবৃত্তি করতে নিয়মিত এক্সপ্রেশন ব্যবহার করুন।

df = (
    bpd.read_gbq_table("bigquery-public-data.iowa_liquor_sales.sales")
    .assign(
        zip_code=lambda _: _["zip_code"].str.replace(".0", "")
    )
)
volume_by_zip = df.groupby("zip_code").agg({"volume_sold_liters": "sum"})
(
  volume_by_zip
  .sort_values("volume_sold_liters", ascending=False)
  .head(25)
  .to_pandas()
  .plot.bar(rot=80)
)

প্রত্যাশিত আউটপুট:

সর্বাধিক বিক্রিত জিপ কোডগুলিতে অ্যালকোহলের পরিমাণের বার চার্ট

৫. বিক্রয়ের মধ্যে পারস্পরিক সম্পর্ক আবিষ্কার করুন

কেন কিছু জিপ কোড অন্যদের তুলনায় বেশি বিক্রি হয়? একটি অনুমান হল যে এটি জনসংখ্যার আকারের পার্থক্যের কারণে। বেশি জনসংখ্যার একটি জিপ কোডে সম্ভবত বেশি মদ বিক্রি হবে।

জনসংখ্যা এবং মদ বিক্রির পরিমাণের মধ্যে পারস্পরিক সম্পর্ক গণনা করে এই অনুমানটি পরীক্ষা করুন।

অন্যান্য ডেটাসেটের সাথে যোগ দিন

মার্কিন আদমশুমারি ব্যুরোর আমেরিকান কমিউনিটি সার্ভে জিপ কোড ট্যাবুলেশন এরিয়া সার্ভে-এর মতো জনসংখ্যা ডেটাসেটের সাথে যোগ দিন।

census_acs = bpd.read_gbq_table("bigquery-public-data.census_bureau_acs.zcta_2020_5yr")

আমেরিকান কমিউনিটি সার্ভে GEOID দ্বারা রাজ্যগুলিকে চিহ্নিত করে। জিপ কোড ট্যাবুলেশন এলাকার ক্ষেত্রে, GEOID জিপ কোডের সমান।

volume_by_pop = volume_by_zip.join(
    census_acs.set_index("geo_id")
)

জিপ কোড ট্যাবুলেশন এলাকার জনসংখ্যার সাথে বিক্রিত লিটার অ্যালকোহলের তুলনা করার জন্য একটি স্ক্যাটার প্লট তৈরি করুন।

(
    volume_by_pop[["volume_sold_liters", "total_pop"]]
    .to_pandas()
    .plot.scatter(x="total_pop", y="volume_sold_liters")
)

প্রত্যাশিত আউটপুট:

জনসংখ্যা এবং বিক্রিত মদের লিটার অনুসারে জিপ কোডের সারণী এলাকার বিক্ষিপ্ত প্লট

পারস্পরিক সম্পর্ক গণনা করুন

প্রবণতাটি মোটামুটি রৈখিক দেখাচ্ছে। জনসংখ্যা মদের বিক্রি কতটা ভালোভাবে ভবিষ্যদ্বাণী করতে পারে তা পরীক্ষা করার জন্য এর সাথে একটি রৈখিক রিগ্রেশন মডেল স্থাপন করুন।

from bigframes.ml.linear_model import LinearRegression

feature_columns = volume_by_pop[["total_pop"]]
label_columns = volume_by_pop[["volume_sold_liters"]]

# Create the linear model
model = LinearRegression()
model.fit(feature_columns, label_columns)

score পদ্ধতি ব্যবহার করে ফিট কতটা ভালো তা পরীক্ষা করুন।

model.score(feature_columns, label_columns).to_pandas()

নমুনা আউটপুট:

	mean_absolute_error	mean_squared_error	mean_squared_log_error	median_absolute_error	r2_score	explained_variance
0	245065.664095	224398167097.364288	5.595021	178196.31289	0.380096	0.380096

জনসংখ্যার মানের একটি পরিসরে predict ফাংশনকে কল করে সেরা ফিট রেখা আঁকুন।

import matplotlib.pyplot as pyplot
import numpy as np
import pandas as pd

line = pd.Series(np.arange(0, 50_000), name="total_pop")
predictions = model.predict(line).to_pandas()

zips = volume_by_pop[["volume_sold_liters", "total_pop"]].to_pandas()
pyplot.scatter(zips["total_pop"], zips["volume_sold_liters"])
pyplot.plot(
  line,
  predictions.sort_values("total_pop")["predicted_volume_sold_liters"],
  marker=None,
  color="red",
)

প্রত্যাশিত আউটপুট:

সবচেয়ে উপযুক্ত লাইন সহ স্ক্যাটার প্লট

হেটেরোসেডাস্টিসিটি মোকাবেলা করা

পূর্ববর্তী চার্টের তথ্য ভিন্নধর্মী বলে মনে হচ্ছে। জনসংখ্যার সাথে সাথে সর্বোত্তম ফিট লাইনের চারপাশের পার্থক্য বৃদ্ধি পায়।

সম্ভবত প্রতি ব্যক্তি ক্রয় করা অ্যালকোহলের পরিমাণ তুলনামূলকভাবে স্থির।

volume_per_pop = (
    volume_by_pop[volume_by_pop['total_pop'] > 0]
    .assign(liters_per_pop=lambda df: df["volume_sold_liters"] / df["total_pop"])
)

(
    volume_per_pop[["liters_per_pop", "total_pop"]]
    .to_pandas()
    .plot.scatter(x="total_pop", y="liters_per_pop")
)

প্রত্যাশিত আউটপুট:

প্রতি জনসংখ্যার লিটারের বিক্ষিপ্ত প্লট

দুটি ভিন্ন উপায়ে ক্রয় করা গড় লিটার অ্যালকোহল গণনা করুন:

  1. আইওয়াতে প্রতি ব্যক্তি গড়ে কত অ্যালকোহল কেনা হয়?
  2. সমস্ত জিপ কোডের উপর গড়ে প্রতি ব্যক্তি কত অ্যালকোহল ক্রয় করেছেন তার পরিমাণ কত?

(১) ছবিতে, এটি সমগ্র রাজ্যে কত পরিমাণ অ্যালকোহল কেনা হয়েছে তা প্রতিফলিত করে। (২) ছবিতে, এটি গড় জিপ কোড প্রতিফলিত করে, যা অগত্যা (১) এর মতো হবে না কারণ বিভিন্ন জিপ কোডের জনসংখ্যা ভিন্ন।

df = (
    bpd.read_gbq_table("bigquery-public-data.iowa_liquor_sales.sales")
    .assign(
        zip_code=lambda _: _["zip_code"].str.replace(".0", "")
    )
)
census_state = bpd.read_gbq(
    "bigquery-public-data.census_bureau_acs.state_2020_5yr",
    index_col="geo_id",
)

volume_per_pop_statewide = (
    df['volume_sold_liters'].sum()
    / census_state["total_pop"].loc['19']
)
volume_per_pop_statewide

প্রত্যাশিত আউটপুট: 87.997

average_per_zip = volume_per_pop["liters_per_pop"].mean()
average_per_zip

প্রত্যাশিত আউটপুট: 67.139

উপরের মতো এই গড়গুলি প্লট করুন।

import numpy as np
import pandas as pd
from matplotlib import pyplot

line = pd.Series(np.arange(0, 50_000), name="total_pop")

zips = volume_per_pop[["liters_per_pop", "total_pop"]].to_pandas()
pyplot.scatter(zips["total_pop"], zips["liters_per_pop"])
pyplot.plot(line, np.full(line.shape, volume_per_pop_statewide), marker=None, color="magenta")
pyplot.plot(line, np.full(line.shape, average_per_zip), marker=None, color="red")

প্রত্যাশিত আউটপুট:

প্রতি জনসংখ্যার লিটারের বিক্ষিপ্ত প্লট

কিছু জিপ কোড এখনও বেশ বড় আকারের, বিশেষ করে কম জনসংখ্যার এলাকায়। কেন এমনটা হয় তা অনুমান করার জন্য এটি একটি অনুশীলন হিসাবে রেখে দেওয়া হয়েছে। উদাহরণস্বরূপ, এমন হতে পারে যে কিছু জিপ কোডে জনসংখ্যা কম কিন্তু ব্যবহার বেশি কারণ সেখানে এলাকার একমাত্র মদের দোকান রয়েছে। যদি তাই হয়, তাহলে আশেপাশের জিপ কোডের জনসংখ্যার ভিত্তিতে গণনা করলে এই বহিরাগতদের সমান করা যেতে পারে।

৬. বিক্রিত মদের প্রকারভেদের তুলনা করা

ভৌগোলিক তথ্য ছাড়াও, আইওয়া মদের খুচরা বিক্রয় ডাটাবেসে বিক্রিত পণ্য সম্পর্কে বিস্তারিত তথ্য রয়েছে। সম্ভবত এগুলি বিশ্লেষণ করে, আমরা ভৌগোলিক অঞ্চল জুড়ে রুচির পার্থক্য প্রকাশ করতে পারি।

বিভাগগুলি অন্বেষণ করুন

ডাটাবেসে আইটেমগুলি শ্রেণীবদ্ধ করা হয়। কয়টি বিভাগ আছে?

import bigframes.pandas as bpd

bpd.options.bigquery.ordering_mode = "partial"
bpd.options.display.repr_mode = "deferred"

df = bpd.read_gbq_table("bigquery-public-data.iowa_liquor_sales.sales")
df.category_name.nunique()

প্রত্যাশিত আউটপুট: 103

ভলিউম অনুসারে সবচেয়ে জনপ্রিয় বিভাগগুলি কোনগুলি?

counts = (
    df.groupby("category_name")
    .agg({"volume_sold_liters": "sum"})
    .sort_values(["volume_sold_liters"], ascending=False)
    .to_pandas()
)
counts.head(25).plot.bar(rot=80)

বিক্রিত শীর্ষ মদের শ্রেণীর বার চার্ট

ARRAY ডেটা টাইপ নিয়ে কাজ করা

হুইস্কি, রাম, ভদকা এবং আরও অনেক কিছুরই বেশ কয়েকটি বিভাগ আছে। আমি এগুলোকে একত্রে ভাগ করতে চাই।

Series.str.split() পদ্ধতি ব্যবহার করে বিভাগের নামগুলিকে আলাদা শব্দে বিভক্ত করে শুরু করুন। explode() পদ্ধতি ব্যবহার করে এটি তৈরি করা অ্যারেটি আননেস্ট করুন।

category_parts = df.category_name.str.split(" ").explode()
counts = (
    category_parts
    .groupby(category_parts)
    .size()
    .sort_values(ascending=False)
    .to_pandas()
)
counts.head(25).plot.bar(rot=80)

বিভাগ থেকে গণনা অনুসারে শব্দ

category_parts.nunique()

প্রত্যাশিত আউটপুট: 113

উপরের চার্টটি দেখলে, তথ্যগুলিতে এখনও VODKA এবং VODKAS আলাদা। বিভাগগুলিকে আরও ছোট সেটে ভাগ করার জন্য আরও গ্রুপিং প্রয়োজন।

৭. BigQuery ডেটাফ্রেমের সাথে NLTK ব্যবহার করা

মাত্র ১০০টি ক্যাটাগরি থাকলে, কিছু হিউরিস্টিক লেখা সম্ভব হবে অথবা এমনকি ক্যাটাগরি থেকে বৃহত্তর লিকার টাইপের জন্য ম্যাপিং তৈরি করাও সম্ভব হবে। বিকল্পভাবে, এই ধরনের ম্যাপিং তৈরি করতে জেমিনির মতো একটি বৃহৎ ল্যাঙ্গুয়েজ মডেল ব্যবহার করা যেতে পারে। কোডল্যাব ব্যবহার করে দেখুন জেমিনির সাথে BigQuery DataFrames ব্যবহার করে আনস্ট্রাকচার্ড ডেটা থেকে অন্তর্দৃষ্টি পান

পরিবর্তে, এই তথ্য প্রক্রিয়াকরণের জন্য একটি আরও ঐতিহ্যবাহী প্রাকৃতিক ভাষা প্রক্রিয়াকরণ প্যাকেজ, NLTK ব্যবহার করুন। উদাহরণস্বরূপ, "স্টেমার" নামক প্রযুক্তি বহুবচন এবং একবচন বিশেষ্যকে একই মানে একত্রিত করতে পারে।

শব্দের স্তম্ভ তৈরিতে NLTK ব্যবহার করা

NLTK প্যাকেজটি প্রাকৃতিক ভাষা প্রক্রিয়াকরণ পদ্ধতি প্রদান করে যা পাইথন থেকে অ্যাক্সেসযোগ্য। এটি ব্যবহার করে দেখতে প্যাকেজটি ইনস্টল করুন।

%pip install nltk

এরপর, প্যাকেজটি আমদানি করুন। সংস্করণটি পরীক্ষা করুন। এটি পরে টিউটোরিয়ালে ব্যবহার করা হবে।

import nltk

nltk.__version__

শব্দের "কান্ড" তৈরি করার জন্য শব্দের মান নির্ধারণের একটি উপায়। এটি বহুবচনের জন্য "s" হিসাবে, যেকোনো প্রত্যয়কে সরিয়ে দেয়।

def stem(word: str) -> str:
    # https://www.nltk.org/howto/stem.html
    import nltk.stem.snowball

    # Avoid failure if a NULL is passed in.
    if not word:
        return word

    stemmer = nltk.stem.snowball.SnowballStemmer("english")
    return stemmer.stem(word)

কয়েকটি শব্দ দিয়ে এটি চেষ্টা করে দেখুন।

stem("WHISKEY")

প্রত্যাশিত আউটপুট: whiskey

stem("WHISKIES")

প্রত্যাশিত আউটপুট: whiski

দুর্ভাগ্যবশত, এটি হুইস্কির সাথে হুইস্কির মিল খুঁজে পায়নি। অনিয়মিত বহুবচনের সাথে স্টেমারগুলি ভালোভাবে কাজ করে না। একটি লেমমাটাইজার ব্যবহার করে দেখুন, যা মূল শব্দ সনাক্ত করার জন্য আরও পরিশীলিত কৌশল ব্যবহার করে, যাকে "লেমা" বলা হয়।

def lemmatize(word: str) -> str:
    # https://stackoverflow.com/a/18400977/101923
    # https://www.nltk.org/api/nltk.stem.wordnet.html#module-nltk.stem.wordnet
    import nltk
    import nltk.stem.wordnet


    # Avoid failure if a NULL is passed in.
    if not word:
        return word

    nltk.download('wordnet')
    wnl = nltk.stem.wordnet.WordNetLemmatizer()
    return wnl.lemmatize(word.lower())

কয়েকটি শব্দ দিয়ে এটি চেষ্টা করে দেখুন।

lemmatize("WHISKIES")

প্রত্যাশিত আউটপুট: whisky

lemmatize("WHISKY")

প্রত্যাশিত আউটপুট: whisky

lemmatize("WHISKEY")

প্রত্যাশিত আউটপুট: whiskey

দুর্ভাগ্যবশত, এই লেম্যাটাইজার "হুইস্কি" শব্দটিকে "হুইস্কি" এর মতো একই লেমার সাথে ম্যাপ করে না। যেহেতু এই শব্দটি আইওয়া খুচরা মদ বিক্রয় ডাটাবেসের জন্য বিশেষভাবে গুরুত্বপূর্ণ, তাই একটি অভিধান ব্যবহার করে আমেরিকান বানানের সাথে ম্যানুয়ালি ম্যাপ করুন।

def lemmatize(word: str) -> str:
    # https://stackoverflow.com/a/18400977/101923
    # https://www.nltk.org/api/nltk.stem.wordnet.html#module-nltk.stem.wordnet
    import nltk
    import nltk.stem.wordnet


    # Avoid failure if a NULL is passed in.
    if not word:
        return word

    nltk.download('wordnet')
    wnl = nltk.stem.wordnet.WordNetLemmatizer()
    lemma = wnl.lemmatize(word.lower())

    table = {
        "whisky": "whiskey",  # Use the American spelling.
    }
    return table.get(lemma, lemma)

কয়েকটি শব্দ দিয়ে এটি চেষ্টা করে দেখুন।

lemmatize("WHISKIES")

প্রত্যাশিত আউটপুট: whiskey

lemmatize("WHISKEY")

প্রত্যাশিত আউটপুট: whiskey

অভিনন্দন! এই লেম্যাটাইজারটি বিভাগগুলি সংকুচিত করার জন্য ভালোভাবে কাজ করবে। BigQuery-এর সাথে এটি ব্যবহার করতে, আপনাকে এটি ক্লাউডে স্থাপন করতে হবে।

ফাংশন স্থাপনের জন্য আপনার প্রকল্প সেটআপ করুন

BigQuery যাতে এই ফাংশনটি অ্যাক্সেস করতে পারে, তার জন্য ক্লাউডে এটি স্থাপন করার আগে, আপনাকে একবারের জন্য কিছু সেটআপ করতে হবে।

একটি নতুন কোড সেল তৈরি করুন এবং your-project-id এই টিউটোরিয়ালের জন্য যে গুগল ক্লাউড প্রোজেক্ট আইডি ব্যবহার করছেন তা দিয়ে প্রতিস্থাপন করুন।

project_id = "your-project-id"

কোনও অনুমতি ছাড়াই একটি পরিষেবা অ্যাকাউন্ট তৈরি করুন, কারণ এই ফাংশনটির কোনও ক্লাউড রিসোর্সে অ্যাক্সেসের প্রয়োজন নেই।

from google.cloud import iam_admin_v1
from google.cloud.iam_admin_v1 import types

iam_admin_client = iam_admin_v1.IAMClient()
request = types.CreateServiceAccountRequest()

account_id = "bigframes-no-permissions"
request.account_id = account_id
request.name = f"projects/{project_id}"

display_name = "bigframes remote function (no permissions)"
service_account = types.ServiceAccount()
service_account.display_name = display_name
request.service_account = service_account

account = iam_admin_client.create_service_account(request=request)
print(account.email)

প্রত্যাশিত আউটপুট: bigframes-no-permissions@your-project-id.iam.gserviceaccount.com

ফাংশনটি ধরে রাখার জন্য একটি BigQuery ডেটাসেট তৈরি করুন।

from google.cloud import bigquery

bqclient = bigquery.Client(project=project_id)
dataset = bigquery.Dataset(f"{project_id}.functions")
bqclient.create_dataset(dataset, exists_ok=True)

একটি রিমোট ফাংশন স্থাপন করা হচ্ছে

যদি এখনও সক্রিয় না থাকে, তাহলে ক্লাউড ফাংশন API সক্ষম করুন।

!gcloud services enable cloudfunctions.googleapis.com

এখন, আপনার তৈরি করা ডেটাসেটে আপনার ফাংশনটি স্থাপন করুন। পূর্ববর্তী ধাপগুলিতে তৈরি করা ফাংশনটিতে একটি @bpd.remote_function ডেকোরেটর যোগ করুন।

@bpd.remote_function(
    dataset=f"{project_id}.functions",
    name="lemmatize",
    # TODO: Replace this with your version of nltk.
    packages=["nltk==3.9.1"],
    cloud_function_service_account=f"bigframes-no-permissions@{project_id}.iam.gserviceaccount.com",
    cloud_function_ingress_settings="internal-only",
)
def lemmatize(word: str) -> str:
    # https://stackoverflow.com/a/18400977/101923
    # https://www.nltk.org/api/nltk.stem.wordnet.html#module-nltk.stem.wordnet
    import nltk
    import nltk.stem.wordnet


    # Avoid failure if a NULL is passed in.
    if not word:
        return word

    nltk.download('wordnet')
    wnl = nltk.stem.wordnet.WordNetLemmatizer()
    lemma = wnl.lemmatize(word.lower())

    table = {
        "whisky": "whiskey",  # Use the American spelling.
    }
    return table.get(lemma, lemma)

স্থাপনে প্রায় দুই মিনিট সময় লাগবে।

রিমোট ফাংশন ব্যবহার করে

স্থাপনা সম্পন্ন হলে, আপনি এই ফাংশনটি পরীক্ষা করতে পারেন।

lemmatize = bpd.read_gbq_function(f"{project_id}.functions.lemmatize")

words = bpd.Series(["whiskies", "whisky", "whiskey", "vodkas", "vodka"])
words.apply(lemmatize).to_pandas()

প্রত্যাশিত আউটপুট:

0	whiskey
1	whiskey
2	whiskey
3	vodka
4	vodka

dtype: string

৮. কাউন্টি অনুসারে অ্যালকোহল সেবনের তুলনা করা

এখন যেহেতু lemmatize ফাংশনটি উপলব্ধ, বিভাগগুলি একত্রিত করতে এটি ব্যবহার করুন।

বিভাগটির সর্বোত্তম সারসংক্ষেপের জন্য শব্দটি খুঁজে বের করা

প্রথমে, ডাটাবেসের সকল বিভাগের একটি ডেটাফ্রেম তৈরি করুন।

df = bpd.read_gbq_table("bigquery-public-data.iowa_liquor_sales.sales")

categories = (
    df['category_name']
    .groupby(df['category_name'])
    .size()
    .to_frame()
    .rename(columns={"category_name": "total_orders"})
    .reset_index(drop=False)
)
categories.to_pandas()

প্রত্যাশিত আউটপুট:

category_name	total_orders
0	100 PROOF VODKA	99124
1	100% AGAVE TEQUILA	724374
2	AGED DARK RUM	59433
3	AMARETTO - IMPORTED	102
4	AMERICAN ALCOHOL	24351
...	...	...
98	WATERMELON SCHNAPPS	17844
99	WHISKEY LIQUEUR	1442732
100	WHITE CREME DE CACAO	7213
101	WHITE CREME DE MENTHE	2459
102	WHITE RUM	436553
103 rows × 2 columns

এরপর, বিরামচিহ্ন এবং "আইটেম" এর মতো কয়েকটি ফিলার শব্দ বাদে, বিভাগগুলির সমস্ত শব্দের একটি ডেটাফ্রেম তৈরি করুন।

words = (
    categories.assign(
        words=categories['category_name']
        .str.lower()
        .str.split(" ")
    )
    .assign(num_words=lambda _: _['words'].str.len())
    .explode("words")
    .rename(columns={"words": "word"})
)
words = words[
    # Remove punctuation and "item", unless it's the only word
    (words['word'].str.isalnum() & ~(words['word'].str.startswith('item')))
    | (words['num_words'] == 1)
]
words.to_pandas()

প্রত্যাশিত আউটপুট:

category_name	total_orders	word	num_words
0	100 PROOF VODKA	99124	100	3
1	100 PROOF VODKA	99124	proof	3
2	100 PROOF VODKA	99124	vodka	3
...	...	...	...	...
252	WHITE RUM	436553	white	2
253	WHITE RUM	436553	rum	2
254 rows × 4 columns

মনে রাখবেন যে গ্রুপিংয়ের পর লেম্যাটাইজ করার মাধ্যমে, আপনি আপনার ক্লাউড ফাংশনের উপর লোড কমাচ্ছেন। ডাটাবেসের কয়েক মিলিয়ন সারির প্রতিটিতে লেম্যাটাইজ ফাংশন প্রয়োগ করা সম্ভব, তবে গ্রুপিংয়ের পর এটি প্রয়োগ করার চেয়ে এটি বেশি ব্যয়বহুল হবে এবং কোটা বৃদ্ধির প্রয়োজন হতে পারে।

lemmas = words.assign(lemma=lambda _: _["word"].apply(lemmatize))
lemmas.to_pandas()

প্রত্যাশিত আউটপুট:

category_name	total_orders	word	num_words	lemma
0	100 PROOF VODKA	99124	100	3	100
1	100 PROOF VODKA	99124	proof	3	proof
2	100 PROOF VODKA	99124	vodka	3	vodka
...	...	...	...	...	...
252	WHITE RUM	436553	white	2	white
253	WHITE RUM	436553	rum	2	rum
254 rows × 5 columns

এখন যেহেতু শব্দগুলি লেম্যাটাইজ করা হয়েছে, আপনাকে সেই লেমা নির্বাচন করতে হবে যা বিভাগটির সর্বোত্তম সারসংক্ষেপ করে। যেহেতু বিভাগগুলিতে অনেক ফাংশন শব্দ নেই, তাই হিউরিস্টিক ব্যবহার করুন যে যদি একটি শব্দ একাধিক অন্যান্য বিভাগে উপস্থিত হয়, তবে এটি সম্ভবত একটি সারসংক্ষেপ শব্দ হিসাবে ভাল (যেমন হুইস্কি)।

lemma_counts = (
    lemmas
    .groupby("lemma", as_index=False)
    .agg({"total_orders": "sum"})
    .rename(columns={"total_orders": "total_orders_with_lemma"})
)

categories_with_lemma_counts = lemmas.merge(lemma_counts, on="lemma")

max_lemma_count = (
    categories_with_lemma_counts
    .groupby("category_name", as_index=False)
    .agg({"total_orders_with_lemma": "max"})
    .rename(columns={"total_orders_with_lemma": "max_lemma_count"})
)

categories_with_max = categories_with_lemma_counts.merge(
    max_lemma_count,
    on="category_name"
)

categories_mapping = categories_with_max[
    categories_with_max['total_orders_with_lemma'] == categories_with_max['max_lemma_count']
].groupby("category_name", as_index=False).max()
categories_mapping.to_pandas()

প্রত্যাশিত আউটপুট:

	category_name	total_orders	word	num_words	lemma	total_orders_with_lemma	max_lemma_count
0	100 PROOF VODKA	99124	vodka	3	vodka	7575769	7575769
1	100% AGAVE TEQUILA	724374	tequila	3	tequila	1601092	1601092
2	AGED DARK RUM	59433	rum	3	rum	3226633	3226633
...	...	...	...	...	...	...	...
100	WHITE CREME DE CACAO	7213	white	4	white	446225	446225
101	WHITE CREME DE MENTHE	2459	white	4	white	446225	446225
102	WHITE RUM	436553	rum	2	rum	3226633	3226633
103 rows × 7 columns

এখন যেহেতু প্রতিটি বিভাগের সারসংক্ষেপের জন্য একটি মাত্র লেমা আছে, এটিকে মূল ডেটাফ্রেমের সাথে মার্জ করুন।

df_with_lemma = df.merge(
    categories_mapping,
    on="category_name",
    how="left"
)
df_with_lemma[df_with_lemma['category_name'].notnull()].peek()

প্রত্যাশিত আউটপুট:

	invoice_and_item_number	...	lemma	total_orders_with_lemma	max_lemma_count
0	S30989000030	...	vodka	7575769	7575769
1	S30538800106	...	vodka	7575769	7575769
2	S30601200013	...	vodka	7575769	7575769
3	S30527200047	...	vodka	7575769	7575769
4	S30833600058	...	vodka	7575769	7575769
5 rows × 30 columns

কাউন্টির তুলনা করা

প্রতিটি কাউন্টিতে বিক্রয়ের তুলনা করে দেখুন কী কী পার্থক্য রয়েছে।

county_lemma = (
    df_with_lemma
    .groupby(["county", "lemma"])
    .agg({"volume_sold_liters": "sum"})
    # Cast to an integer for more deterministic equality comparisons.
    .assign(volume_sold_int64=lambda _: _['volume_sold_liters'].astype("Int64"))
)

প্রতিটি কাউন্টিতে সর্বাধিক বিক্রিত পণ্য (লেমা) খুঁজুন।

county_max = (
    county_lemma
    .reset_index(drop=False)
    .groupby("county")
    .agg({"volume_sold_int64": "max"})
)

county_max_lemma = county_lemma[
    county_lemma["volume_sold_int64"] == county_max["volume_sold_int64"]
]

county_max_lemma.to_pandas()

প্রত্যাশিত আউটপুট:

	volume_sold_liters	volume_sold_int64
county	lemma		
SCOTT	vodka	6044393.1	6044393
APPANOOSE	whiskey	292490.44	292490
HAMILTON	whiskey	329118.92	329118
...	...	...	...
WORTH	whiskey	100542.85	100542
MITCHELL	vodka	158791.94	158791
RINGGOLD	whiskey	65107.8	65107
101 rows × 2 columns

কাউন্টিগুলি একে অপরের থেকে কতটা আলাদা?

county_max_lemma.groupby("lemma").size().to_pandas()

প্রত্যাশিত আউটপুট:

lemma	
american	1
liqueur	1
vodka	15
whiskey	83

dtype: Int64

বেশিরভাগ কাউন্টিতে, পরিমাণের দিক থেকে হুইস্কি সবচেয়ে জনপ্রিয় পণ্য, যেখানে ১৫টি কাউন্টিতে ভদকা সবচেয়ে জনপ্রিয়। রাজ্যজুড়ে সবচেয়ে জনপ্রিয় মদের ধরণের সাথে এটি তুলনা করুন।

total_liters = (
    df_with_lemma
    .groupby("lemma")
    .agg({"volume_sold_liters": "sum"})
    .sort_values("volume_sold_liters", ascending=False)
)
total_liters.to_pandas()

প্রত্যাশিত আউটপুট:

	volume_sold_liters
lemma	
vodka	85356422.950001
whiskey	85112339.980001
rum	33891011.72
american	19994259.64
imported	14985636.61
tequila	12357782.37
cocktails/rtd	7406769.87
...

হুইস্কি এবং ভদকার পরিমাণ প্রায় একই, রাজ্যজুড়ে হুইস্কির চেয়ে ভদকা একটু বেশি।

অনুপাতের তুলনা

প্রতিটি কাউন্টির বিক্রয়ের অনন্য বৈশিষ্ট্য কী? রাজ্যের অন্যান্য অংশ থেকে কাউন্টিটি কী আলাদা?

রাজ্যব্যাপী বিক্রির অনুপাতের উপর ভিত্তি করে কোন মদের বিক্রির পরিমাণ প্রত্যাশিত পরিমাণের চেয়ে সবচেয়ে বেশি আনুপাতিকভাবে আলাদা তা খুঁজে বের করতে কোহেনের h পরিমাপ ব্যবহার করুন।

import numpy as np

total_proportions = total_liters / total_liters.sum()
total_phi = 2 * np.arcsin(np.sqrt(total_proportions))

county_liters = df_with_lemma.groupby(["county", "lemma"]).agg({"volume_sold_liters": "sum"})
county_totals = df_with_lemma.groupby(["county"]).agg({"volume_sold_liters": "sum"})
county_proportions = county_liters / county_totals
county_phi = 2 * np.arcsin(np.sqrt(county_proportions))

cohens_h = (
    (county_phi - total_phi)
    .rename(columns={"volume_sold_liters": "cohens_h"})
    .assign(cohens_h_int=lambda _: (_['cohens_h'] * 1_000_000).astype("Int64"))
)

এখন যেহেতু প্রতিটি লেমার জন্য কোহেনের h পরিমাপ করা হয়েছে, প্রতিটি কাউন্টিতে রাজ্যব্যাপী অনুপাত থেকে বৃহত্তম পার্থক্যটি খুঁজুন।

# Note: one might want to use the absolute value here if interested in counties
# that drink _less_ of a particular liquor than expected.
largest_per_county = cohens_h.groupby("county").agg({"cohens_h_int": "max"})
counties = cohens_h[cohens_h['cohens_h_int'] == largest_per_county["cohens_h_int"]]
counties.sort_values('cohens_h', ascending=False).to_pandas()

প্রত্যাশিত আউটপুট:

	cohens_h	cohens_h_int
county	lemma		
EL PASO	liqueur	1.289667	1289667
ADAMS	whiskey	0.373591	373590
IDA	whiskey	0.306481	306481
OSCEOLA	whiskey	0.295524	295523
PALO ALTO	whiskey	0.293697	293696
...	...	...	...
MUSCATINE	rum	0.053757	53757
MARION	rum	0.053427	53427
MITCHELL	vodka	0.048212	48212
WEBSTER	rum	0.044896	44895
CERRO GORDO	cocktails/rtd	0.027496	27495
100 rows × 2 columns

কোহেনের h মান যত বেশি হবে, রাজ্যের গড়ের তুলনায় সেই ধরণের অ্যালকোহল গ্রহণের পরিমাণে পরিসংখ্যানগতভাবে উল্লেখযোগ্য পার্থক্য থাকার সম্ভাবনা তত বেশি। ছোট ধনাত্মক মানের ক্ষেত্রে, ব্যবহারের পার্থক্য রাজ্যব্যাপী গড়ের চেয়ে আলাদা, তবে এটি এলোমেলো পার্থক্যের কারণে হতে পারে।

অন্যদিকে: EL PASO কাউন্টি আইওয়াতে একটি কাউন্টি বলে মনে হচ্ছে না, এটি এই ফলাফলের উপর সম্পূর্ণ নির্ভর করার আগে ডেটা পরিষ্কারের আরেকটি প্রয়োজনের ইঙ্গিত দিতে পারে।

কাউন্টিগুলি কল্পনা করা

প্রতিটি কাউন্টির ভৌগোলিক এলাকা পেতে bigquery-public-data.geo_us_boundaries.counties টেবিলের সাথে যোগ দিন। মার্কিন যুক্তরাষ্ট্র জুড়ে কাউন্টির নাম অনন্য নয়, তাই শুধুমাত্র আইওয়া থেকে কাউন্টিগুলি অন্তর্ভুক্ত করার জন্য ফিল্টার করুন। আইওয়ার জন্য FIPS কোড হল '19'।

counties_geo = (
    bpd.read_gbq("bigquery-public-data.geo_us_boundaries.counties")
    .assign(county=lambda _: _['county_name'].str.upper())
)
counties_plus = (
    counties
    .reset_index(drop=False)
    .merge(counties_geo[counties_geo['state_fips_code'] == '19'], on="county", how="left")
    .dropna(subset=["county_geom"])
    .to_pandas()
)
counties_plus

প্রত্যাশিত আউটপুট:

county	lemma	cohens_h	cohens_h_int	geo_id	state_fips_code	...
0	ALLAMAKEE	american	0.087931	87930	19005	19	...
1	BLACK HAWK	american	0.106256	106256	19013	19	...
2	WINNESHIEK	american	0.093101	93101	19191	19	...
...	...	...	...	...	...	...	...	...	...	...	...	...	...	...	...	...	...	...	...	...	...
96	CLINTON	tequila	0.075708	75707	19045	19	...
97	POLK	tequila	0.087438	87438	19153	19	...
98	LEE	schnapps	0.064663	64663	19111	19	...
99 rows × 23 columns

মানচিত্রে এই পার্থক্যগুলি কল্পনা করতে জিওপ্যান্ডাস ব্যবহার করুন।

import geopandas

counties_plus = geopandas.GeoDataFrame(counties_plus, geometry="county_geom")

# https://stackoverflow.com/a/42214156/101923
ax = counties_plus.plot(figsize=(14, 14))
counties_plus.apply(
    lambda row: ax.annotate(
        text=row['lemma'],
        xy=row['county_geom'].centroid.coords[0],
        ha='center'
    ),
    axis=1,
)

প্রতিটি কাউন্টিতে রাজ্যব্যাপী বিক্রির পরিমাণের অনুপাতের তুলনায় সবচেয়ে আলাদা অ্যালকোহলের একটি মানচিত্র

9. পরিষ্কার করা

যদি আপনি এই টিউটোরিয়ালের জন্য একটি নতুন Google ক্লাউড প্রকল্প তৈরি করে থাকেন, তাহলে টেবিল বা অন্যান্য তৈরি করা সম্পদের জন্য অতিরিক্ত চার্জ রোধ করতে আপনি এটি মুছে ফেলতে পারেন।

অথবা, এই টিউটোরিয়ালের জন্য তৈরি ক্লাউড ফাংশন, পরিষেবা অ্যাকাউন্ট এবং ডেটাসেটগুলি মুছে ফেলুন।

১০. অভিনন্দন!

তুমি BigQuery DataFrames ব্যবহার করে স্ট্রাকচার্ড ডেটা পরিষ্কার এবং বিশ্লেষণ করেছ। এই পথ ধরে তুমি Google Cloud-এর পাবলিক ডেটাসেট, BigQuery Studio-তে Python নোটবুক, BigQuery ML, BigQuery রিমোট ফাংশন এবং BigQuery DataFrames-এর শক্তি অন্বেষণ করেছ। অসাধারণ কাজ!

পরবর্তী পদক্ষেপ