Ibis
How to get rid of stringly typed analytics
ABOUT ME
NOT A FAN oF STRINGLY TYPED CODE
EXPLAIN PLAN
- What's an ibis?
- Why ibis?
- Demo
- Design
- Execution
- Questions
UPSHOT
- SQL + Python for analytics
String programming
What's an ibis?
An ibis

Thanks @tswast!
Another ibis

ibis
- Python library for analytic SQL
- Inspired by dplyr
- "Pandonic"?
- "Ibisish"?
- "Birdlike"?
Example
In [32]: import ibis
In [33]: ibis.options.interactive = True
In [34]: con = ibis.sqlite.connect('crunchbase.db')
In [35]: con.list_tables()
Out[35]: ['acquisitions', 'companies', 'investments', 'rounds']
In [36]: t = con.table('companies')
In [37]: t.name.nunique()
Out[37]: 51063
In [38]: t.region.nunique()
Out[38]: 1034
In [39]: (t.groupby('region')
...: .aggregate(topk=t.funding_rounds.count())
...: .sort_by(ibis.desc('topk'))
...: .head())
...:
Out[39]:
region topk
0 SF Bay Area 7165
1 None 5071
2 New York City 2724
3 Boston 1906
4 London 1679Why IBIS?

SQl & Productivity
SELECT *,
acq_ipos / num_investments::float AS acq_rate
FROM (
SELECT
CASE
WHEN i.investor_name IS NULL THEN 'NO INVESTOR'
ELSE i.investor_name
END AS "Investor name",
COUNT(DISTINCT c.permalink) AS num_investments,
COUNT(
DISTINCT
CASE
WHEN c.status IN ('ipo', 'acquired') THEN
c.permalink
ELSE NULL
END
) AS acq_ipos
FROM crunchbase_companies
LEFT JOIN crunchbase_investments
ON c.permalink = i.company_permalink
GROUP BY 1
ORDER BY 2 DESC
) aSELECT *,
acq_ipos / num_investments::float AS acq_rate
FROM (
SELECT
CASE
WHEN i.investor_name IS NULL THEN 'NO INVESTOR'
ELSE i.investor_name
END AS "Investor name",
COUNT(DISTINCT c.permalink) AS num_investments,
COUNT(
DISTINCT
CASE
WHEN c.status IN ('ipo', 'acquired') THEN
c.permalink
ELSE NULL
END
) AS acq_ipos
FROM crunchbase_companies AS c
LEFT JOIN crunchbase_investments AS i
ON c.permalink = i.company_permalink
GROUP BY 1
ORDER BY 2 DESC
) aAsk me if you want access to the data for this query
SQl & Productivity
Lines of SQL Written
Productivity
WHY SQL?








WHY SQL?








SQL
PYTHON
PYTHON
-
Good
- Easy to learn/use
- Rich ecosystem
-
Not so good
- Analytics limited in scale
- SQL interaction



MAKE SQL PRODUCTIVE FROM PYTHON
"I knoW, I'll just generate STRINGS"
"STRINGLY" Typed
"""
SELECT *,
acq_ipos/num_investments::FLOAT AS acquitision_rate
FROM (
SELECT CASE WHEN i.investor_name IS NULL THEN 'NO INVESTOR'
ELSE i.investor_name END "Investor name",
{agg}(DISTINCT c.permalink) AS number_of_investments,
COUNT(DISTINCT
{case_with_conditions}
AS {my_name}
FROM {}
LEFT JOIN {}
ON c.permalink = i.company_permalink
GROUP BY 1
ORDER BY 2 DESC
) a
""".format(
generate_left_part_of_join(use_templating='obviously'),
generate_right_join_v2(sometimes_do_not_use_templating='what else would I do'),
agg=next(agg_generator()),
case_with_conditions='CASE {} END'.format(
'WHEN {} THEN {}'.format(k, v) for k, v in conditions
),
my_name='acqusitions or whatever'
)

"I knoW, I'll just generate STRINGS"
"I knoW, I'll just generate STRINGS write CoNCISE PYTHON code that type checks and eventually generates strings"
recap
- Python
- Fail fastlyish
- Convenience
- Performance
DEMO time
Potential drawbacks
- Performance
- Abstraction
- Features
DESIGN
DESIGN PRinCIPLES
- Separate API from operation
- Check types ASAP
t.string_col + 1.0API
Impl
class Expr:
def __init__(self, impl):
self.impl = impl
def op(self):
return self.impl
class StringValue(Expr):
def concat(self, other):
return ops.Concat(
self, other
).to_expr()class Node:
def to_expr(self):
output_type = self.output_type()
return output_type(self)
class Concat(ops.Node):
left = Arg(rules.string)
right = Arg(rules.string)
def output_type(self):
return rules.shape_like(
self.args,
dtype=dt.string
)- Exprs are the API
- Based on element type, shape
- Used for type checking
- Implementation details
- Know how to make exprs
- Used for compilation
DESIGN - SEPARATION
DESIGN - operations
- Nodes (Ops) are implementation
- Holds arguments
- Determines the Expr type
class Repeat(ValueOp):
arg = Arg(rlz.string)
times = Arg(rlz.integer)
def output_type(self):
return rlz.shape_like(
self.args, dt.string
)class StringJoin(ValueOp):
sep = Arg(rlz.string)
arg = Arg(
rlz.list_of(
rlz.string, min_length=1
)
)
def output_type(self):
return rlz.shape_like(
tuple(self.flat_args()),
dt.string
)Execution phases
- Expression graph construction
- Compilation
- Execution
EXPReSSION GRAPH

t.bigint_col.bitwise_and(where=(t.bigint_col == 10) | (t.bigint_col == 40))COmpilation

t.bigint_col.bitwise_and(where=(t.bigint_col == 10) | (t.bigint_col == 40))BITWISE_AND(t.bigint_col) WHERE (t.bigint_col = 10 OR t.bigint_col = 40)BITWISE_AND({}.bigint_col) WHERE ({})BITWISE_AND({}) WHERE ({})BITWISE_AND(t.bigint_col) WHERE ({})BITWISE_AND(t.bigint_col) WHERE ({} OR {})BITWISE_AND(t.bigint_col) WHERE ({} = {} OR {})BITWISE_AND(t.bigint_col) WHERE (t.bigint_col = {} OR {})BITWISE_AND(t.bigint_col) WHERE (t.bigint_col = 10 OR {})BITWISE_AND(t.bigint_col) WHERE (t.bigint_col = 10 OR t.bigint_col = {})Execution
- Give string to driver
- Convert results into pandas things
lots to do
-
Inline SQL: expr.sql("SELECT * FROM t") - Returning something other than DataFrames
- Usability
- TPC-xBB
- Roadmap for 2022 is in the works!
QUESTIONS?
Ibis
By Phillip Cloud
Ibis
Ibis is a Python library for making it easy to work with different flavors of SQL engines, with a focus on big data systems such as Apache Impala, Google BigQuery and others.
- 2,047