Loading...

Spam

This example uses the spam dataset to detect SMS spam.

  • v1: the SMS type (spam or ham).

  • v2: SMS content.

We will follow the data science cycle (Data Exploration - Data Preparation - Data Modeling - Model Evaluation - Model Deployment) to solve this problem.

Initialization

This example uses the following version of vastorbit:

import vastorbit as vo

vo.__version__

Connect to VAST. This example uses an existing connection called VASTDSN . For details on how to create a connection, see the Connection tutorial. You can skip the below cell if you already have an established connection.

vo.connect("VASTDSN")

Let’s create a VastFrame of the dataset. The dataset is available here.

spam = vo.read_csv("spam.csv")

Let’s take a look at the first few entries in the dataset.

spam
Abc
type
Varchar(50)
100%
Abc
content
Varchar(1010)
100%
1hamK.then any other special?
2hamCarlos is taking his sweet time as usual so let me know when you and patty are done/want to smoke and I'll tell him to haul ass
3hamOk pa. Nothing problem:-)
4hamHave you heard about that job? I'm going to that wildlife talk again tonight if u want2come. Its that2worzels and a wizzle or whatever it is?!
5hamGod picked up a flower and dippeditinaDEW, lovingly touched itwhichturnedinto u, and the he gifted tomeandsaid,THIS FRIEND IS 4U
6hamWhen you came to hostel.
7hamOk no prob... I'll come after lunch then...
8hamJus telling u dat i'll b leaving 4 shanghai on 21st instead so we'll haf more time 2 meet up cya...
9hamAre your freezing ? Are you home yet ? Will you remember to kiss your mom in the morning? Do you love me ? Do you think of me ? Are you missing me yet ?
10hamYou all ready for * big day tomorrow?
11hamI'll probably be around mu a lot
12ham645
13spamRT-KIng Pro Video Club>> Need help? info@ringtoneking.co.uk or call 08701237397 You must be 16+ Club credits redeemable at www.ringtoneking.co.uk! Enjoy!
14hamThnx dude. u guys out 2nite?
15hamMe sef dey laugh you. Meanwhile how's my darling anjie!
16hamMm i had my food da from out
17hamK, makes sense, btw carlos is being difficult so you guys are gonna smoke while I go pick up the second batch and get gas
18hamDid u download the fring app?
19hamThe 2 oz guy is being kinda flaky but one friend is interested in picking up $ <#> worth tonight if possible
20hamFriends that u can stay on fb chat with

Data Exploration and Preparation

Our dataset relies on text analysis. First, we should create some features. For example, we can use the SMS length and label encoding on the ‘type’ to get a dummy (1 if the message is a SPAM, 0 otherwise). We should also convert the message content to lowercase to simplify our analysis.

import vastorbit.sql.functions as fun

spam["length"] = fun.length(spam["content"])
spam["text_index"] = fun.row_number()._over(by = "content") # index for tfidf
spam["content"].apply("LOWER({})")
spam["type"].decode('spam', 1, 0)
123
type
Integer
100%
Abc
content
Varchar(1010)
100%
123
length
Bigint
100%
123
text_index
Bigint
100%
11i don't know u and u don't know me. send chat to 86688 now and let's find each other! only 150p/msg rcvd. hg/suite342/2lands/row/w1j6hl ldn. 18 years or over.1581
20lol please do. actually send a pic of yourself right now. i wanna see. pose with a comb and hair dryer or something.1161
31valentines day special! win over å£1000 in our quiz and take your partner on the trip of a lifetime! send go to 83600 now. 150p/msg rcvd. custcare:087187202011581
40back in brum! thanks for putting us up and keeping us all and happy. see you soon 821
50rose needs water, season needs change, poet needs imagination..my phone needs ur sms and i need ur lovely frndship forever....1261
60* am on my way141
70aiyah e rain like quite big leh. if drizzling i can at least run home.701
80the monthly amount is not that terrible and you will not pay anything till 6months after finishing school.1061
90package all your programs well301
100oh fine, i'll be by tonight271
110i'll reach in ard 20 mins ok...311
120i also thk too fast... xy suggest one not me. u dun wan it's ok. going 2 rain leh where got gd.951
130i dont want to hear anything281
141knock knock txt whose there to 80082 to enter r weekly draw 4 a å£250 gift voucher 4 a store of yr choice. t&cs www.tkls.com age16 to stoptxtstopå£1.50/week1561
150hey pple...$700 or $900 for 5 nights...excellent location wif breakfast hamper!!!811
160feel like trying kadeem again? :v331
170that's cool, i'll come by like <#> ish451
180i am 6 ft. we will be a good combination!411
190i'm okay. chasing the dream. what's good. what are you doing next.661
201urgent! we are trying to contact u. todays draw shows that you have won a å£800 prize guaranteed. call 09050003091 from land line. claim c52. valid 12hrs only1581

Let’s compute some statistics using the length of the message.

spam['type'].describe(
    method = 'cat_stats',
    numcol = 'length',
)
countpercentmeanstdminapprox_10%approx_25%approx_50%approx_75%approx_90%max
174713.406317300789663138.866131191432429.1830823664389171399132149157161224
0482586.5936826992103371.0236269430051858.02227532043164224335292144910

Note

Spam tends to be longer than a normal message. First, let’s create a view with just spam. Then, we’ll use the TfidfVectorizer to create a dictionary and identify keywords.

spams = spam.search(spam["type"] == 1)

from vastorbit.machine_learning.vast import TfidfVectorizer

dict_spams = TfidfVectorizer()
dict_spams.fit(spams, index = "text_index", x = "content")
dict_spams = dict_spams.transform(spams, index = "text_index", x = "content")
dict_spams
123
row_id
Bigint
100%
Abc
word
Varchar(511565)
100%
123
tf
Double
100%
123
idf
Double
100%
123
tfidf
Double
100%
13find1.01.22314355131420970.06915497637343532
23rcvd1.01.22314355131420970.06915497637343532
3350001.01.00.05653872458317062
43161.01.22314355131420970.06915497637343532
53bonus1.01.22314355131420970.06915497637343532
63866882.01.22314355131420970.13830995274687063
73lets1.01.22314355131420970.06915497637343532
83only1.01.22314355131420970.06915497637343532
93no11.01.22314355131420970.06915497637343532
103tone1.01.22314355131420970.06915497637343532
113365041.01.22314355131420970.06915497637343532
123750001.01.22314355131420970.06915497637343532
133text1.01.22314355131420970.06915497637343532
143now2.01.22314355131420970.13830995274687063
153dont2.01.22314355131420970.13830995274687063
163freephone1.01.00.05653872458317062
173camera2.01.22314355131420970.13830995274687063
183to4.01.22314355131420970.27661990549374127
193welcome1.01.22314355131420970.06915497637343532
203still1.01.22314355131420970.06915497637343532

Let’s add the most occurent words in our VastFrame and compute the correlation vector.

for word in dict_spams.groupby("word", "SUM(tf) AS cnt").sort({"cnt": "desc"}).head(200).values["word"]:
    if word not in ['content', 'length', 'type'] : # because there is already a column called content, length and type
        try:
            spam.regexp(
                name = word,
                pattern = word,
                method = "count",
                column = "content",
            )
        except:
            pass
spam.corr(focus = "type")

Let’s just keep the first 100-most correlated features and merge the numbers together.

words = spam.corr(focus = "type", show = False)
spam.drop(columns = words["index"][101:])

for word in words["index"][1:101]:
    if any(char.isdigit() for char in word):
        spam[word].drop()

spam.regexp(
    column = "content",
    pattern = "([0-9])+",
    method = "count",
    name = "nb_numbers",
)
123
type
Integer
100%
Abc
content
Varchar(1010)
100%
123
length
Bigint
100%
123
to
Bigint
100%
123
a
Bigint
100%
123
call
Bigint
100%
123
your
Bigint
100%
123
free
Bigint
100%
123
now
Bigint
100%
123
or
Bigint
100%
123
txt
Bigint
100%
123
u
Bigint
100%
123
on
Bigint
100%
123
ur
Bigint
100%
123
from
Bigint
100%
123
mobile
Bigint
100%
123
text
Bigint
100%
123
claim
Bigint
100%
123
stop
Bigint
100%
123
reply
Bigint
100%
123
prize
Bigint
100%
123
our
Bigint
100%
123
won
Bigint
100%
123
new
Bigint
100%
123
nokia
Bigint
100%
...
123
selected
Bigint
100%
123
network
Bigint
100%
123
mob
Bigint
100%
123
offer
Bigint
100%
123
weekly
Bigint
100%
123
valid
Bigint
100%
123
cost
Bigint
100%
123
collect
Bigint
100%
123
attempt
Bigint
100%
123
r
Bigint
100%
123
bonus
Bigint
100%
123
sae
Bigint
100%
123
vouchers
Bigint
100%
123
rate
Bigint
100%
123
c
Bigint
100%
123
await
Bigint
100%
123
unsubscribe
Bigint
100%
123
land
Bigint
100%
123
poly
Bigint
100%
123
pobox
Bigint
100%
123
winner
Bigint
100%
123
top
Bigint
100%
123
wk
Bigint
100%
123
voucher
Bigint
100%
123
nb_numbers
Bigint
100%
10k.then any other special?250200000000000000000000...0000000001000010000000000
20carlos is taking his sweet time as usual so let me know when you and patty are done/want to smoke and i'll tell him to haul ass12721100010041000000000000...0000000002000010000000000
30ok pa. nothing problem:-)250100000000000000000000...0000000001000000000000000
40have you heard about that job? i'm going to that wildlife talk again tonight if u want2come. its that2worzels and a wizzle or whatever it is?! 14321300002031000000000000...0000000004000010000000002
50god picked up a flower and dippeditinadew, lovingly touched itwhichturnedinto u, and the he gifted tomeandsaid,this friend is 4u1283600000050100000000000...0000000003000030000000001
60when you came to hostel.241100000010000000000000...0000000000000010000000000
70ok no prob... i'll come after lunch then...430100000010000000000000...0000000002000020000000000
80jus telling u dat i'll b leaving 4 shanghai on 21st instead so we'll haf more time 2 meet up cya...990700001031000000000000...0000000001000010000000003
90are your freezing ? are you home yet ? will you remember to kiss your mom in the morning? do you love me ? do you think of me ? are you missing me yet ?1521302101070200000002000...0000000009000000000000000
100you all ready for * big day tomorrow?371300002010000000000000...0000000004000000000000000
110i'll probably be around mu a lot320300000020000000000000...0000000002000000000000000
12064530000000000000000000000...0000000000000000000000001
131rt-king pro video club>> need help? info@ringtoneking.co.uk or call 08701237397 you must be 16+ club credits redeemable at www.ringtoneking.co.uk! enjoy!1532310001062000000000000...0000000007000060000000002
140thnx dude. u guys out 2nite?280000000040000000000000...0000000000000000000000001
150me sef dey laugh you. meanwhile how's my darling anjie!550400000020000000000000...0000000001000000000000000
160mm i had my food da from out280200000010010000000000...0000000001000000000000000
170k, makes sense, btw carlos is being difficult so you guys are gonna smoke while i go pick up the second batch and get gas1210700000042000000000000...0000000002000050000000000
180did u download the fring app?290200000010000000000000...0000000001000000000000000
190the 2 oz guy is being kinda flaky but one friend is interested in picking up $ <#> worth tonight if possible1151200001032000000000000...0000000003000010000000001
200friends that u can stay on fb chat with390400000011000000000000...0000000001000020000000000

Let’s narrow down our keyword list to words of more than two characters.

columns = spam.get_columns()
for word in columns:
    if len(word.replace('"', '')) <= 2:
        spam[word].drop()

Compute the correlation vector again using the response column.

spam.corr(focus = "type")

We have enough correlated features with our response to create a fantastic model.


Machine Learning

The LogisticRegression classifier is a powerful and performant algorithm for text analytics and binary classification. Before using it on our data, let’s use a cross-validation to test the efficiency of our model.

from vastorbit.machine_learning.vast import LogisticRegression

model = LogisticRegression(max_iter=1000)

from vastorbit.machine_learning.model_selection import cross_validate

cross_validate(
    model,
    spam,
    spam.get_columns(exclude_columns = ["type", "content"]),
    "type",
    cv = 5,
)
aucprc_aucaccuracylog_lossprecisionrecallf1_scoremccinformednessmarkednesscsitime
1-fold0.98658255922587150.097043312878796080.9826958105646630.0624488790873654050.97580645161290320.88321167883211680.92720306513409970.91887980872068840.88008993065313670.95937934689011060.86428571428571430.45777010917663574
2-fold0.9849043729038350.10215242604477770.9832746478873240.064787358307327930.97945205479452060.899371069182390.93770491803278690.92919193315458310.89630044482210320.96329043863290440.88271604938271610.3983190059661865
3-fold0.97542246931249290.109106553543071750.97787610619469020.073270449382664610.96240601503759390.86486486486486490.9110320284697510.90011316395606540.85977321517036390.94234583449596920.83660130718954250.33335399627685547
4-fold0.97886458419642870.111737434091923720.9759143621766280.076877635822662880.95522388059701490.85906040268456370.90459363957597170.89249763244211570.8528875631783910.9339472848523340.82580645161290320.4583089351654053
5-fold0.98960158255057080.105708580573016460.97482014388489210.075103174607592920.96551724137931040.85889570552147240.90909090909090910.89659810026677660.85362700162263150.9417323396212960.83333333333333340.35788416862487793
avg0.98307511363783970.105149661426317140.97891621414163940.070497499441522750.96768112868426860.87308074421708160.91792491206070380.90745612770804570.86853563108932530.94813904889852290.84854857116084190.4011272430419922
std0.0051871896790329660.0051781162836719620.0034684874266981210.005779163204671790.008857401835175890.0158688562660967460.0124948552567843030.0141316537141796820.0170193766906041430.0112420846237908470.0214779832472942150.0508905155321698

We have an excellent model! Let’s learn from the data.

model.fit(
    spam,
    spam.get_columns(exclude_columns = ["type", "content"]),
    "type",
)
model.confusion_matrix()

Our model can reliably identify spam.

Conclusion

We’ve solved our problem in a pandas-like way, all without ever loading data into memory!