Analisis data eksplorasi penjualan minuman keras Iowa menggunakan paket BigQuery DataFrames

Analisis data eksplorasi penjualan minuman keras Iowa menggunakan paket BigQuery DataFrames

Tentang codelab ini

subjectTerakhir diperbarui Des 4, 2024
account_circleDitulis oleh Tim Swena

1. Ringkasan

Di lab ini, Anda akan menggunakan BigQuery DataFrames dari notebook Python di BigQuery Studio untuk membersihkan dan menganalisis set data publik penjualan minuman keras Iowa. Manfaatkan kemampuan BigQuery ML dan fungsi jarak jauh untuk menemukan insight.

Anda akan membuat notebook Python untuk membandingkan penjualan di seluruh area geografis. Hal ini dapat disesuaikan agar berfungsi pada data terstruktur apa pun.

Tujuan

Di lab ini, Anda akan mempelajari cara melakukan tugas berikut:

  • Mengaktifkan dan menggunakan notebook Python di BigQuery Studio
  • Menghubungkan ke BigQuery menggunakan paket BigQuery DataFrames
  • Membuat regresi linear menggunakan BigQuery ML
  • Melakukan agregasi dan penggabungan yang kompleks menggunakan sintaksis seperti pandas yang sudah dikenal

2. Persyaratan

  • Browser, seperti Chrome atau Firefox
  • Project Google Cloud yang mengaktifkan penagihan

Sebelum memulai

Untuk mengikuti petunjuk dalam codelab ini, Anda memerlukan Project Google Cloud dengan BigQuery Studio yang diaktifkan dan akun penagihan yang terhubung.

  1. Di Konsol Google Cloud, di halaman pemilih project, pilih atau buat project Google Cloud
  2. Pastikan penagihan diaktifkan untuk project Google Cloud Anda. Pelajari cara memeriksa apakah penagihan telah diaktifkan pada suatu project
  3. Ikuti petunjuk untuk Mengaktifkan BigQuery Studio untuk pengelolaan aset.

Menyiapkan BigQuery Studio

Buat notebook kosong dan hubungkan ke runtime.

  1. Buka BigQuery Studio di Konsol Google Cloud.
  2. Klik di samping tombol +.
  3. Pilih Python notebook.
  4. Tutup pemilih template.
  5. Pilih + Kode untuk membuat sel kode baru.
  6. Instal paket BigQuery DataFrames versi terbaru dari sel kode.Ketik perintah berikut.
    %pip install --upgrade bigframes --quiet
    Klik tombol Run cell atau tekan Shift + Enter untuk menjalankan sel kode.

3. Membaca set data publik

Lakukan inisialisasi paket BigQuery DataFrames dengan menjalankan kode berikut di sel kode baru:

import bigframes.pandas as bpd

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

Catatan: dalam tutorial ini, kita menggunakan "mode pengurutan parsial" eksperimental, yang memungkinkan kueri yang lebih efisien saat digunakan dengan pemfilteran seperti pandas. Beberapa fitur pandas yang memerlukan pengurutan atau indeks yang ketat mungkin tidak berfungsi.

Periksa versi paket bigframes Anda dengan

bpd.__version__

Tutorial ini memerlukan versi 1.27.0 atau yang lebih baru.

Penjualan retail minuman keras Iowa

Set data penjualan retail minuman keras Iowa disediakan di BigQuery melalui program set data publik Google Cloud. Set data ini berisi setiap pembelian minuman keras grosir di Negara Bagian Iowa oleh retailer untuk dijual kepada individu sejak 1 Januari 2012. Data dikumpulkan oleh Divisi Minuman Beralkohol di dalam Departemen Perdagangan Iowa.

Di BigQuery, buat kueri bigquery-public-data.iowa_liquor_sales.sales untuk menganalisis penjualan retail minuman keras Iowa. Gunakan metode bigframes.pandas.read_gbq() untuk membuat DataFrame dari string kueri atau ID tabel.

Jalankan kode berikut di sel kode baru untuk membuat DataFrame bernama "df":

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

Menemukan informasi dasar tentang DataFrame

Gunakan metode DataFrame.peek() untuk mendownload sampel kecil data.

Jalankan sel ini:

df.peek()

Output yang diharapkan:

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       ...                            

Catatan: head() memerlukan pengurutan dan umumnya kurang efisien daripada peek() jika Anda ingin memvisualisasikan sampel data.

Sama seperti pandas, gunakan properti DataFrame.dtypes untuk melihat semua kolom yang tersedia dan jenis data yang sesuai. Data ini ditampilkan dengan cara yang kompatibel dengan pandas.

Jalankan sel ini:

df.dtypes

Output yang diharapkan:

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

Metode DataFrame.describe() membuat kueri beberapa statistik dasar dari DataFrame. Jalankan DataFrame.to_pandas() untuk mendownload statistik ringkasan ini sebagai DataFrame pandas.

Jalankan sel ini:

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

Output yang diharapkan:

        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

4. Memvisualisasikan dan membersihkan data

Set data penjualan retail minuman keras Iowa memberikan informasi geografis yang terperinci, termasuk lokasi toko retail. Gunakan data ini untuk mengidentifikasi tren dan perbedaan di seluruh area geografis.

Memvisualisasikan penjualan per kode pos

Ada beberapa metode visualisasi bawaan seperti DataFrame.plot.hist(). Gunakan metode ini untuk membandingkan penjualan minuman keras menurut kode pos.

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

Output yang diharapkan:

Histogram volume

Gunakan diagram batang untuk melihat kode pos mana yang menjual alkohol terbanyak.

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

Output yang diharapkan:

Diagram batang volume alkohol di kode pos dengan penjualan teratas

Membersihkan data

Beberapa kode pos memiliki .0 di akhir. Mungkin di suatu tempat dalam pengumpulan data, kode pos tidak sengaja dikonversi menjadi nilai floating point. Gunakan ekspresi reguler untuk membersihkan kode pos dan mengulangi analisis.

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)
)

Output yang diharapkan:

Diagram batang volume alkohol di kode pos dengan penjualan teratas

5. Menemukan korelasi dalam penjualan

Mengapa beberapa kode pos menjual lebih banyak daripada yang lain? Salah satu hipotesisnya adalah karena perbedaan ukuran populasi. Kode pos dengan populasi yang lebih banyak kemungkinan akan menjual lebih banyak minuman keras.

Uji hipotesis ini dengan menghitung korelasi antara populasi dan volume penjualan minuman keras.

Menggabungkan dengan set data lain

Gabungkan dengan set data populasi seperti survei area tabulasi kode pos Survei Komunitas Amerika dari Badan Sensus AS.

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

American Community Survey mengidentifikasi negara bagian berdasarkan GEOID. Untuk area tabulasi kode pos, GEOID sama dengan kode pos.

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

Buat diagram pencar untuk membandingkan populasi area tabulasi kode pos dengan liter alkohol yang terjual.

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

Output yang diharapkan:

Diagram sebar area tabulasi kode pos menurut populasi dan liter minuman keras yang terjual

Menghitung korelasi

Tren ini terlihat kira-kira linear. Sesuaikan model regresi linear dengan hal ini untuk memeriksa seberapa baik populasi dapat memprediksi penjualan minuman keras.

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)

Periksa seberapa baik kecocokannya dengan menggunakan metode score.

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

Contoh output:

        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

Menggambar garis yang paling sesuai, tetapi memanggil fungsi predict pada rentang nilai populasi.

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",
)

Output yang diharapkan:

Plot sebar dengan garis yang paling sesuai

Mengatasi heteroskedasitas

Data dalam diagram sebelumnya tampaknya heteroskedastis. Variansi di sekitar garis yang paling cocok akan bertambah seiring dengan populasi.

Mungkin jumlah alkohol yang dibeli per orang relatif konstan.

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")
)

Output yang diharapkan:

Diagram sebar liter per populasi

Hitung rata-rata liter alkohol yang dibeli dengan dua cara berbeda:

  1. Berapa jumlah rata-rata alkohol yang dibeli per orang di Iowa?
  2. Berapa rata-rata jumlah alkohol yang dibeli per orang di semua kode pos.

Di (1), nilai ini mencerminkan jumlah alkohol yang dibeli di seluruh negara bagian. Di (2), kode pos rata-rata ditampilkan, yang tidak selalu sama dengan (1) karena kode pos yang berbeda memiliki populasi yang berbeda.

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

Output yang diharapkan: 87.997

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

Output yang diharapkan: 67.139

Buat plot rata-rata ini, mirip dengan di atas.

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")

Output yang diharapkan:

Diagram sebar liter per populasi

Masih ada beberapa kode pos yang merupakan outlier yang cukup besar, terutama di area dengan populasi yang lebih sedikit. Anda dapat membuat hipotesis tentang penyebabnya. Misalnya, mungkin beberapa kode pos memiliki populasi rendah tetapi konsumsi tinggi karena hanya memiliki satu toko minuman keras di area tersebut. Jika demikian, penghitungan berdasarkan populasi kode pos di sekitarnya dapat mengecualikan outlier ini.

6. Membandingkan jenis minuman keras yang dijual

Selain data geografis, database penjualan retail minuman keras Iowa juga berisi informasi mendetail tentang item yang terjual. Mungkin dengan menganalisis hal ini, kita dapat mengungkapkan perbedaan selera di seluruh area geografis.

Jelajahi kategori

Item dikategorikan dalam database. Berapa banyak kategorinya?

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()

Output yang diharapkan: 103

Manakah kategori yang paling populer berdasarkan volume?

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)

Diagram batang kategori minuman keras teratas yang terjual

Bekerja dengan jenis data ARRAY

Ada beberapa kategori untuk setiap wiski, rum, vodka, dan lainnya. Saya ingin mengelompokkan semuanya.

Mulai dengan memisahkan nama kategori menjadi kata-kata terpisah menggunakan metode Series.str.split(). Hapus array yang dibuat menggunakan metode 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)

Kata berdasarkan jumlah dari kategori

category_parts.nunique()

Output yang diharapkan: 113

Melihat diagram di atas, data masih memiliki VODKA yang terpisah dari VODKAS. Pengelompokan lainnya diperlukan untuk menciutkan kategori ke dalam kumpulan yang lebih kecil.

7. Menggunakan NLTK dengan BigQuery DataFrames

Dengan hanya sekitar 100 kategori, Anda dapat menulis beberapa heuristik atau bahkan membuat pemetaan dari kategori ke jenis minuman keras yang lebih luas secara manual. Atau, Anda dapat menggunakan model bahasa besar seperti Gemini untuk membuat pemetaan tersebut. Coba codelab Mendapatkan insight dari data tidak terstruktur menggunakan BigQuery DataFrames untuk menggunakan BigQuery DataFrames dengan Gemini.

Sebagai gantinya, gunakan paket natural language processing yang lebih tradisional, NLTK, untuk memproses data ini. Teknologi yang disebut "stemmer" dapat menggabungkan kata benda jamak dan tunggal menjadi nilai yang sama, misalnya.

Menggunakan NLTK untuk meruncingkan kata

Paket NLTK menyediakan metode natural language processing yang dapat diakses dari Python. Instal paket untuk mencobanya.

%pip install nltk

Selanjutnya, impor paket. Periksa versinya. Kode ini akan digunakan nanti dalam tutorial.

import nltk

nltk.__version__

Salah satu cara menstandarkan kata untuk "memotong" kata. Tindakan ini akan menghapus akhiran, seperti "s" di akhir untuk bentuk jamak.

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)

Coba lakukan ini pada beberapa kata.

stem("WHISKEY")

Output yang diharapkan: whiskey

stem("WHISKIES")

Output yang diharapkan: whiski

Sayangnya, hal ini tidak memetakan wiski ke yang sama dengan wiski. Stemmer tidak berfungsi dengan baik dengan bentuk jamak yang tidak beraturan. Coba lemmatizer, yang menggunakan teknik yang lebih canggih untuk mengidentifikasi kata dasar, yang disebut "lemma".

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())

Coba lakukan ini pada beberapa kata.

lemmatize("WHISKIES")

Output yang diharapkan: whisky

lemmatize("WHISKY")

Output yang diharapkan: whisky

lemmatize("WHISKEY")

Output yang diharapkan: whiskey

Sayangnya, lemmatizer ini tidak memetakan "whisky" ke lemma yang sama dengan "whiskies". Karena kata ini sangat penting untuk database penjualan minuman keras retail Iowa, petakan secara manual ke ejaan Amerika menggunakan kamus.

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)

Coba lakukan ini pada beberapa kata.

lemmatize("WHISKIES")

Output yang diharapkan: whiskey

lemmatize("WHISKEY")

Output yang diharapkan: whiskey

Selamat! Lemmatizer ini akan berfungsi dengan baik untuk mempersempit kategori. Untuk menggunakannya dengan BigQuery, Anda harus men-deploynya ke cloud.

Menyiapkan project untuk deployment fungsi

Sebelum men-deploynya ke cloud agar BigQuery dapat mengakses fungsi ini, Anda harus melakukan beberapa penyiapan satu kali.

Buat sel kode baru dan ganti your-project-id dengan project ID Google Cloud yang Anda gunakan untuk tutorial ini.

project_id = "your-project-id"

Buat akun layanan tanpa izin apa pun, karena fungsi ini tidak memerlukan akses ke resource cloud apa pun.

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)

Output yang diharapkan: bigframes-no-permissions@your-project-id.iam.gserviceaccount.com

Buat set data BigQuery untuk menyimpan fungsi.

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)

Men-deploy fungsi jarak jauh

Aktifkan Cloud Functions API jika belum diaktifkan.

!gcloud services enable cloudfunctions.googleapis.com

Sekarang, deploy fungsi ke set data yang baru saja Anda buat. Tambahkan dekorator @bpd.remote_function ke fungsi yang Anda buat di langkah sebelumnya.

@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)

Deployment memerlukan waktu sekitar dua menit.

Menggunakan fungsi jarak jauh

Setelah deployment selesai, Anda dapat menguji fungsi ini.

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

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

Output yang diharapkan:

0       whiskey
1       whiskey
2       whiskey
3       vodka
4       vodka

dtype: string

8. Membandingkan konsumsi alkohol menurut county

Setelah fungsi lemmatize tersedia, gunakan fungsi tersebut untuk menggabungkan kategori.

Menemukan kata yang paling tepat untuk meringkas kategori

Pertama, buat DataFrame dari semua kategori dalam database.

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()

Output yang diharapkan:

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

Selanjutnya, buat DataFrame dari semua kata dalam kategori, kecuali beberapa kata pengisi seperti tanda baca dan "item".

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()

Output yang diharapkan:

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

Perhatikan bahwa dengan melakukan lemmatisasi setelah pengelompokan, Anda mengurangi beban pada Cloud Functions. Anda dapat menerapkan fungsi lemmatize pada setiap dari beberapa juta baris dalam database, tetapi biayanya akan lebih mahal daripada menerapkannya setelah pengelompokan dan mungkin memerlukan penambahan kuota.

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

Output yang diharapkan:

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

Setelah kata dilemmatisasi, Anda perlu memilih lemma yang paling meringkas kategori. Karena tidak ada banyak kata fungsi dalam kategori, gunakan heuristik bahwa jika kata muncul dalam beberapa kategori lain, kata tersebut mungkin lebih baik sebagai kata ringkasan (misalnya, wiski).

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()

Output yang diharapkan:

        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

Setelah ada satu lemma yang merangkum setiap kategori, gabungkan lemma ini ke DataFrame asli.

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

Output yang diharapkan:

        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

Membandingkan wilayah

Bandingkan penjualan di setiap kabupaten untuk melihat perbedaannya.

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"))
)

Temukan produk (lemma) yang paling banyak terjual di setiap county.

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()

Output yang diharapkan:

        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

Seberapa berbeda setiap county satu sama lain?

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

Output yang diharapkan:

lemma   
american        1
liqueur 1
vodka   15
whiskey 83

dtype: Int64

Di sebagian besar county, wiski adalah produk paling populer berdasarkan volume, dengan vodka paling populer di 15 county. Bandingkan dengan jenis minuman keras yang paling populer di seluruh negara bagian.

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

Output yang diharapkan:

        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
...

Volume wiski dan vodka hampir sama, dengan vodka sedikit lebih tinggi daripada wiski di seluruh negara bagian.

Membandingkan proporsi

Apa yang unik dari penjualan di setiap wilayah? Apa yang membuat county ini berbeda dari negara bagian lainnya?

Gunakan pengukuran h Cohen untuk menemukan volume penjualan minuman keras yang paling berbeda secara proporsional dari yang diperkirakan berdasarkan proporsi penjualan di seluruh negara bagian.

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"))
)

Setelah h Cohen diukur untuk setiap lemma, temukan perbedaan terbesar dari proporsi seluruh negara bagian di setiap county.

# 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()

Output yang diharapkan:

        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

Makin besar nilai h Cohen, makin besar kemungkinan adanya perbedaan yang signifikan secara statistik dalam jumlah jenis alkohol yang dikonsumsi dibandingkan dengan rata-rata negara bagian. Untuk nilai positif yang lebih kecil, perbedaan konsumsi berbeda dengan rata-rata seluruh negara bagian, tetapi mungkin disebabkan oleh perbedaan acak.

Catatan: EL PASO county tampaknya bukan county di Iowa. Hal ini mungkin menunjukkan kebutuhan lain untuk pembersihan data sebelum sepenuhnya bergantung pada hasil ini.

Memvisualisasikan wilayah

Gabungkan dengan tabel bigquery-public-data.geo_us_boundaries.counties untuk mendapatkan area geografis untuk setiap county. Nama county tidak unik di seluruh Amerika Serikat, jadi filter untuk hanya menyertakan county dari Iowa. Kode FIPS untuk Iowa adalah '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

Output yang diharapkan:

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

Gunakan GeoPandas untuk memvisualisasikan perbedaan ini pada peta.

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,
)

Peta alkohol yang paling berbeda dari proporsi volume penjualan di seluruh negara bagian di setiap county

9. Pembersihan

Jika telah membuat project Google Cloud baru untuk tutorial ini, Anda dapat menghapusnya untuk mencegah tagihan tambahan untuk tabel atau resource lain yang dibuat.

Atau, hapus Cloud Functions, akun layanan, dan set data yang dibuat untuk tutorial ini.

10. Selamat!

Anda telah membersihkan dan menganalisis data terstruktur menggunakan DataFrame BigQuery. Selama prosesnya, Anda telah menjelajahi Set Data Publik Google Cloud, notebook Python di BigQuery Studio, BigQuery ML, BigQuery Remote Functions, dan kecanggihan BigQuery DataFrames. Hebat!

Langkah berikutnya