WEBVTT

00:00:00.001 --> 00:00:05.020
Hello and welcome to Python Bytes, where we deliver Python news and headlines directly to your earbuds.

00:00:05.020 --> 00:00:10.020
This is episode 259, recorded November 17th, 2021.

00:00:10.020 --> 00:00:11.400
And I'm Brian Okken.

00:00:11.400 --> 00:00:12.500
I'm Michael Kennedy.

00:00:12.500 --> 00:00:13.680
And I'm Renee Teet.

00:00:13.680 --> 00:00:15.620
Well, thanks, Renee, for joining us today.

00:00:15.620 --> 00:00:17.820
Can you tell us a little bit about who you are?

00:00:17.820 --> 00:00:20.980
I'm the director of data science at Helio Campus.

00:00:20.980 --> 00:00:26.160
And a lot of people know me as Data Science Renee or Becoming Data Sci on Twitter.

00:00:26.160 --> 00:00:29.560
So that's where a lot of people follow me.

00:00:29.760 --> 00:00:35.740
And then I started with, I had a podcast that's not actively recording, but it's called Becoming a Data Scientist Podcast.

00:00:35.740 --> 00:00:39.820
So some people listening probably know me from that as well.

00:00:39.820 --> 00:00:40.460
Cool.

00:00:40.460 --> 00:00:41.300
Yeah, awesome.

00:00:41.300 --> 00:00:42.680
You were doing a bunch of cool stuff there.

00:00:42.680 --> 00:00:45.360
And any chances of maybe going back to podcasting?

00:00:45.360 --> 00:00:47.100
It's definitely still open.

00:00:47.100 --> 00:00:50.380
I've always told myself this is a pause, not a stop.

00:00:50.380 --> 00:00:51.680
It's just an extended pause.

00:00:51.680 --> 00:00:53.400
So yes, hopefully I will get back to it.

00:00:53.400 --> 00:00:54.740
It's hard to keep going, isn't it?

00:00:54.740 --> 00:00:57.900
I mean, life gets in the way and then you get busy.

00:00:58.320 --> 00:01:02.980
I'm always so impressed with those of you that have hundreds of episodes very consistently recorded.

00:01:02.980 --> 00:01:05.240
Well, Brian makes me show up every week.

00:01:05.240 --> 00:01:09.540
So yeah, it definitely helps having a partner so that you can coerce each other in.

00:01:09.540 --> 00:01:10.200
That's right.

00:01:10.740 --> 00:01:14.300
Well, Michael, speaking of partners, want to tell us about something?

00:01:14.300 --> 00:01:16.060
Let's talk about some changes.

00:01:16.060 --> 00:01:17.920
Some PyPI changes.

00:01:17.920 --> 00:01:19.960
These come to us from Brian Skin.

00:01:19.960 --> 00:01:22.100
Thank you, Brian, for shooting this over.

00:01:22.540 --> 00:01:26.020
And it's a project by Bernay Gabor here.

00:01:26.020 --> 00:01:36.180
And if we pull this up, it says, have you ever wondered when did your Python packages, the packages in your environment that you have active or any given environment, how old are they?

00:01:36.180 --> 00:01:38.160
When were they last updated?

00:01:38.160 --> 00:01:40.020
Is there a version of them that's out of date?

00:01:40.020 --> 00:01:51.500
So I've been solving this by just forcing them to update using pip compile and the pip-tools stuff to just regenerate and reinstall the requirements files.

00:01:51.680 --> 00:01:55.740
But this is a way to just ask the question, hey, what's the status?

00:01:55.740 --> 00:02:00.200
And it wouldn't be an episode if we didn't somehow feature Will McGugan.

00:02:00.200 --> 00:02:03.920
So this is based on Rich, of course.

00:02:03.920 --> 00:02:05.980
So let's go check this thing out.

00:02:05.980 --> 00:02:11.900
So over here, if we go to the homepage, we get, as all projects should, a nice animation here.

00:02:11.900 --> 00:02:18.800
And if you look at it, you can just see type IPI dash changes and you specify the path to a Python in a virtual environment.

00:02:18.800 --> 00:02:23.200
So in this example, it's like PyPI changes, VNV slash bin slash Python.

00:02:23.200 --> 00:02:27.520
It does some thinking on the internet, caches some information about the packages.

00:02:27.520 --> 00:02:30.440
And it says, all right, you've got all these things installed.

00:02:30.440 --> 00:02:31.300
They're this version.

00:02:31.300 --> 00:02:35.620
Some of it will just say this was updated 10 months ago or a year and three days ago.

00:02:35.620 --> 00:02:41.220
Others, it'll say it was updated a year ago, but only six months on the latest version.

00:02:41.220 --> 00:02:42.580
It says remote such and such.

00:02:42.580 --> 00:02:44.460
That's the one you could install if you were to update it.

00:02:44.460 --> 00:02:55.960
So it's a real nice way to see, well, which ones are here that could be updated or even also sometimes it's interesting to know like, oh, this library, it doesn't have an update, but it's 10 years old.

00:02:55.960 --> 00:03:01.200
Maybe I should consider switching to a library that's a little more maintained and making progress.

00:03:01.200 --> 00:03:01.640
Right.

00:03:01.640 --> 00:03:02.420
What do you all think?

00:03:02.420 --> 00:03:03.180
That's handy.

00:03:03.180 --> 00:03:04.240
Cool, right?

00:03:04.240 --> 00:03:05.340
It is pretty neat.

00:03:05.680 --> 00:03:13.960
So, yeah, I've been playing with this today, installed it, checked it out, even pointed out that, you know, since yesterday, some things changed in one of my projects that I want to keep up to date.

00:03:13.960 --> 00:03:14.980
So I updated it.

00:03:14.980 --> 00:03:15.420
Yeah.

00:03:15.420 --> 00:03:16.100
So I like it.

00:03:16.100 --> 00:03:18.360
It's got a nice command line interface.

00:03:18.360 --> 00:03:24.260
You basically specify the Python that is in the environment that you want to check.

00:03:24.260 --> 00:03:28.580
That could either be the main Python or a environmental virtual environment Python.

00:03:28.940 --> 00:03:36.340
Like I said, you can control the caching because the first time it runs, it has to go get lots of information about each package that's installed and it's faster the second time.

00:03:36.340 --> 00:03:38.240
It also has some cool parallelism.

00:03:38.240 --> 00:03:41.480
So you can say number of jobs like --jobs.

00:03:41.480 --> 00:03:46.860
And by default, it runs 10 downloads in parallel as it's pulling this information in.

00:03:46.860 --> 00:03:48.580
But I guess you could go crazy there.

00:03:48.580 --> 00:03:50.600
So anyway, I thought this was pretty cool.

00:03:50.600 --> 00:03:52.480
It's a nice little thing to have.

00:03:52.480 --> 00:03:54.660
So I pipx installed this.

00:03:54.660 --> 00:03:58.800
It's perfect for pipx because it doesn't need to be in the project.

00:03:58.800 --> 00:03:59.540
It's testing.

00:03:59.540 --> 00:04:02.120
It just needs to be on your machine as a command.

00:04:02.120 --> 00:04:06.600
And then you point it at the environment, different environments, and it gives you reports on those environments.

00:04:06.600 --> 00:04:08.180
Yeah, I love pipx too.

00:04:08.180 --> 00:04:19.940
One of the things I want to note, just as I know a lot of package maintainers, having, I mean, it's worth checking things out if it's a really old packet, if it hasn't been updated for a long time.

00:04:19.940 --> 00:04:25.320
But some things are pure Python things that just do a little tiny thing and don't need updated very often.

00:04:25.320 --> 00:04:30.460
So it's not necessarily a bad thing that it's not updated, but it's an indicator.

00:04:30.460 --> 00:04:31.160
It's an indicator.

00:04:31.160 --> 00:04:31.580
Yeah.

00:04:31.580 --> 00:04:32.220
Of something.

00:04:32.220 --> 00:04:33.480
Yes, exactly.

00:04:33.480 --> 00:04:34.500
Let's see.

00:04:34.500 --> 00:04:39.800
Out in the live audience, Anthony Listerhate says, can the changes be exported to a text file?

00:04:39.800 --> 00:04:43.760
I haven't seen anything about that other than just, you know, piping it into a text file.

00:04:43.760 --> 00:04:47.980
And who knows what happens with all the color in there, but perhaps.

00:04:47.980 --> 00:04:48.600
Yeah.

00:04:48.600 --> 00:04:52.300
Renee, what do you do to manage your dependencies and all those kinds of things?

00:04:52.300 --> 00:04:55.460
Well, at work, we started using Docker for that.

00:04:55.460 --> 00:05:02.400
So we have a centralized Docker container that everyone on my team uses, and we make sure we have the same setup in there.

00:05:02.400 --> 00:05:10.060
So I'm not the one that directly manages it, but that's the solution that we've gone towards to make sure we're all on the same page.

00:05:10.060 --> 00:05:10.760
Oh, interesting.

00:05:10.760 --> 00:05:19.320
So you've got the Docker environment that has some version of Python set up with all the libraries you need pre-installed, and then you just use that to run, and that way you know it's the same.

00:05:19.680 --> 00:05:26.560
Yep. And then it's also nice because when we kind of move some of our projects into production, we can include that Docker container with it.

00:05:26.560 --> 00:05:28.960
So it will have whatever version it had at the time.

00:05:28.960 --> 00:05:37.780
So if for some reason it's not compatible with some later version we upgraded to, it still lives out there with the version of the tools that it had until we have a chance to update everything.

00:05:38.020 --> 00:05:50.560
One of the challenges that people have sometimes is they say, even though you've got some kind of version management, iProject.toml or requirements.txt or whatever, that doesn't necessarily mean that people actually install them the latest.

00:05:50.560 --> 00:05:52.260
So you could still be out of sync, right?

00:05:52.260 --> 00:05:57.280
So having the image that's constantly the same, constantly in sync, that's the kind of way to force it.

00:05:57.280 --> 00:06:01.780
I also want to give a quick shout out to this project, pip.dep.tree.

00:06:01.780 --> 00:06:04.600
Remember, Brian, we spoke about that before, which is pretty cool.

00:06:04.600 --> 00:06:12.300
And what it'll show you is this will show you the things you've directly installed versus the things that happen to be installed.

00:06:12.300 --> 00:06:18.640
So if we go back to this like animation here, you can see that it's got Flask, which is 202.

00:06:18.640 --> 00:06:21.460
But then it's got markup safe.

00:06:21.460 --> 00:06:22.600
It's got it's dangerous.

00:06:22.600 --> 00:06:24.300
Like nobody installed it's dangerous.

00:06:24.300 --> 00:06:26.800
That's a thing that was installed because of Flask.

00:06:26.800 --> 00:06:34.160
And so, for example, when I look at my environments, there were some things that were out of date, but they were out of date because they were pinned requirements.

00:06:34.160 --> 00:06:36.740
Of other things that I actually wanted to install.

00:06:36.740 --> 00:06:41.700
So, for example, example, doc opt and some other things are pinned to lower versions.

00:06:41.700 --> 00:06:45.160
And I can't really update those, but they'll show up as outdated.

00:06:45.160 --> 00:06:51.800
So you might pair this with some pip.dep.tree to see like what ones are you in control of and what ones are just kind of out there.

00:06:51.800 --> 00:06:52.720
That's pretty cool.

00:06:52.720 --> 00:06:53.640
That's that one.

00:06:53.640 --> 00:06:54.760
What do you got for us?

00:06:55.220 --> 00:07:00.840
Well, this is an interesting discussion about a possible change to future Python.

00:07:00.840 --> 00:07:04.440
Again, this is just stuff that people are discussing.

00:07:04.440 --> 00:07:06.820
It's nothing that's even decided on.

00:07:06.820 --> 00:07:12.180
But it's an idea of late bound arguments for Python.

00:07:12.180 --> 00:07:17.800
For late bound arguments for defaults.

00:07:17.800 --> 00:07:20.460
That's it for functions.

00:07:20.920 --> 00:07:22.080
Here's the idea.

00:07:22.080 --> 00:07:33.840
So we know that if you sign the default value for a function argument, that is bound at definition time.

00:07:33.840 --> 00:07:36.320
So when Python first goes and reads it.

00:07:36.320 --> 00:07:38.640
That seems fine.

00:07:38.640 --> 00:07:41.920
It's a weird thing about the namespace there, though.

00:07:41.920 --> 00:07:52.980
So what happens is if you have a variable foo, for instance, or a value foo, the value expression can be looked.

00:07:52.980 --> 00:07:56.200
You can look that up in the defining area.

00:07:56.200 --> 00:07:58.200
So the namespace where the function is defined.

00:07:58.200 --> 00:08:02.140
It's a little specific, but it causes some weirdness.

00:08:02.140 --> 00:08:04.440
It's not the namespace of the function.

00:08:04.440 --> 00:08:06.920
It's the namespace of the surrounding the function.

00:08:07.400 --> 00:08:21.140
The problem with that really is that, like, for instance, if we wanted to do something like a bisect function that took, you know, has a given an array and maybe an x value for the middle or something.

00:08:21.140 --> 00:08:22.800
We also have a high and low.

00:08:22.800 --> 00:08:25.760
We know the low index would be zero as a default.

00:08:25.760 --> 00:08:29.460
But what the high should be is should be the length of the array.

00:08:30.000 --> 00:08:35.480
And it's you can't do that because you can't reference the array as a default value.

00:08:35.480 --> 00:08:45.580
So that's what this kind of what this discussion is about is trying to figure out a way to possibly have an optional late binding of those values.

00:08:45.580 --> 00:08:55.280
And and in this specific case, it'd be very helpful to be able to late bind that value, like at the time that the function is called, not at the time that it's defined.

00:08:55.660 --> 00:09:02.080
And this was you want to take the first parameter and use it to set the default value of the subsequent parameter.

00:09:02.080 --> 00:09:02.680
Yeah.

00:09:02.680 --> 00:09:07.300
Like to say, like the length of the array is the default for length or something.

00:09:07.300 --> 00:09:14.760
And that's the that was was it Chris Angelico that suggested this.

00:09:14.860 --> 00:09:21.320
And the discussion actually got has some people even even Guido said, I'm not really opposed to it.

00:09:21.320 --> 00:09:23.020
Let's let's explore it a little bit.

00:09:23.020 --> 00:09:26.680
So there is some Chris is trying to do a proof of concept.

00:09:26.680 --> 00:09:30.840
There is some question about what the syntax was should be.

00:09:30.840 --> 00:09:35.200
So Chris suggested a equal colon.

00:09:35.200 --> 00:09:40.540
So like the reverse of the walrus operator, because apparently that's available.

00:09:40.540 --> 00:09:46.220
Another suggestion was equal greater than to kind of look like an arrow.

00:09:46.220 --> 00:09:49.520
But we already have like dash arrow to mean something else.

00:09:49.740 --> 00:09:52.280
So up in the air on the syntax.

00:09:52.280 --> 00:10:01.140
But anyway, one of the things I wanted to comment about is the in the article we're linking to.

00:10:01.140 --> 00:10:08.100
It says at first blush, Angelico's idea to fix this wart in Python seems fairly straightforward.

00:10:08.320 --> 00:10:12.080
But the discussion has shown that there are multiple facets to consider.

00:10:12.080 --> 00:10:12.500
Oh, yeah.

00:10:12.500 --> 00:10:16.680
And it's always tricky to add complexity or to the language.

00:10:16.680 --> 00:10:21.420
So, you know, the people in the steering council will take it.

00:10:21.420 --> 00:10:22.360
Think about it.

00:10:22.360 --> 00:10:22.600
Right.

00:10:22.600 --> 00:10:24.020
Under consideration.

00:10:24.020 --> 00:10:24.420
Yes.

00:10:24.420 --> 00:10:24.680
OK.

00:10:24.680 --> 00:10:26.400
Renee, what do you think about this?

00:10:26.400 --> 00:10:27.420
I'm going to be honest.

00:10:27.420 --> 00:10:29.160
It's going over my head a little bit.

00:10:29.160 --> 00:10:32.760
I don't consider myself like a real software developer.

00:10:32.760 --> 00:10:36.360
So I usually use Python for, you know, standard data science type of scripts.

00:10:37.260 --> 00:10:41.640
I'm trying to sit here thinking of a use case for this that I would use and not coming up with one.

00:10:41.640 --> 00:10:43.980
Yeah, I'm with you on that one as well.

00:10:43.980 --> 00:10:46.560
That doesn't mean it's a bad idea necessarily.

00:10:46.560 --> 00:10:55.060
Well, one use case would be to be able to set an empty list as a default value.

00:10:55.060 --> 00:10:58.460
You can't do that now because the list is bound.

00:10:58.460 --> 00:11:03.960
All calls to the function will get whatever the last function set it to.

00:11:03.960 --> 00:11:07.100
And that's a weirdness in Python, but we could probably fix that with this.

00:11:07.100 --> 00:11:07.620
Yeah.

00:11:07.620 --> 00:11:08.100
Yeah.

00:11:08.100 --> 00:11:14.540
That's what I was thinking as well, is if you pass immutable value as the default, then you're asking for trouble.

00:11:14.540 --> 00:11:14.840
Right.

00:11:14.840 --> 00:11:20.240
Because if it gets changed anywhere, then every subsequent call gets those changes applied to it.

00:11:20.240 --> 00:11:21.400
So that seems useful.

00:11:21.400 --> 00:11:24.880
This like sort of flowing one parameter into the next.

00:11:25.820 --> 00:11:27.980
I'm not sure it's worth the complexity.

00:11:27.980 --> 00:11:38.660
So, Renee, what I wanted to ask you was, as somebody who doesn't dive deep into the low levels of the language and like compiler parsing, all that kind of stuff, which is totally fine.

00:11:38.660 --> 00:11:40.480
That's like 99% of the people.

00:11:40.480 --> 00:11:44.060
How do you feel about these kinds of new features coming along?

00:11:44.060 --> 00:11:46.700
Are you like, oh, geez, now I got to learn the walrus operator.

00:11:46.700 --> 00:11:48.400
I got to learn pattern matching.

00:11:48.400 --> 00:11:49.360
I was fine.

00:11:49.360 --> 00:11:50.500
And now I got to deal with this code.

00:11:50.500 --> 00:11:50.960
What is this?

00:11:50.960 --> 00:11:52.480
Or do you see it as like, oh, awesome.

00:11:52.480 --> 00:11:53.160
Here's new stuff.

00:11:53.500 --> 00:11:57.260
Yeah, I mean, I guess it depends how much it really impacts my day-to-day work.

00:11:57.260 --> 00:12:05.260
If it's something that it's not impacting something I use frequently or it's kind of abstracted away from me or optional, then, you know, go ahead.

00:12:05.260 --> 00:12:13.000
But if it's something that some, you know, some features they roll out clearly have a wide-ranging impact and you have to go update everything.

00:12:13.000 --> 00:12:20.340
So I'm not great at keeping up with that, which is one reason that, you know, of course, you have to be so careful when you update to a new version.

00:12:20.660 --> 00:12:24.260
But, you know, I guess that's why people listen to podcasts like this.

00:12:24.260 --> 00:12:25.820
So you know it's potentially coming.

00:12:25.820 --> 00:12:26.720
So you're aware.

00:12:26.720 --> 00:12:27.360
I guess so.

00:12:27.360 --> 00:12:29.880
When it does come out, you're on top of it.

00:12:29.880 --> 00:12:32.220
But I don't have strong opinions.

00:12:32.220 --> 00:12:35.560
And what we worry about a lot in data science is the packages, right?

00:12:35.560 --> 00:12:40.240
So not the base Python, but the packages are constantly changing and the dependencies and the versions.

00:12:40.240 --> 00:12:45.120
So that does end up affecting us when it follows through to that level.

00:12:45.120 --> 00:12:48.880
Yeah, my concern is around teaching Python.

00:12:49.180 --> 00:12:55.740
Because every new syntax thing you put in makes it something that you potentially have to teach somebody.

00:12:55.740 --> 00:13:01.720
And maybe you don't have to teach newbies this, but they'll see it in code.

00:13:01.720 --> 00:13:03.900
So they should be able to understand what it is.

00:13:04.740 --> 00:13:12.940
But on the other hand, like things like you can do really crazy comprehensions, list comprehensions and stuff, but you don't have to.

00:13:12.940 --> 00:13:14.920
And most of the ones I see are fairly simple ones.

00:13:14.920 --> 00:13:17.360
So I don't think we should nix a phone.

00:13:17.360 --> 00:13:20.740
Nick shouldn't nix something just because it can be complicated.

00:13:20.740 --> 00:13:21.360
Anyway.

00:13:21.360 --> 00:13:22.220
Yeah.

00:13:22.220 --> 00:13:22.980
Indeed.

00:13:22.980 --> 00:13:23.260
Yeah.

00:13:23.260 --> 00:13:23.620
Good one.

00:13:23.620 --> 00:13:24.060
All right.

00:13:24.060 --> 00:13:24.880
Renee, you got the next one.

00:13:24.880 --> 00:13:25.560
All right.

00:13:25.560 --> 00:13:29.840
So speaking of data science packages, a lot of us use Pandas.

00:13:29.840 --> 00:13:34.120
So I wrote a book, which I'll come back to later, called SQL for Data Scientists.

00:13:34.960 --> 00:13:45.820
And since I wrote that and, you know, some people that have been learning data science in school or, you know, on the job haven't always used SQL or if they use it as kind of a separate process from their Python.

00:13:45.940 --> 00:13:48.980
So they started asking me, how do you use SQL alongside Python?

00:13:48.980 --> 00:13:54.060
So this is kind of beginner level, but also something that's just very useful.

00:13:54.060 --> 00:13:58.420
In the Pandas package, there's a read SQL function.

00:13:58.420 --> 00:14:00.840
And so you can read a SQL query.

00:14:00.840 --> 00:14:02.300
It runs the query.

00:14:02.300 --> 00:14:04.360
It's kind of a wrapper around some other functions.

00:14:04.360 --> 00:14:08.580
It will run the query and return the data set into your data frame.

00:14:08.580 --> 00:14:14.680
And so basically you're just running a query and the results become the data frame right in your notebook.

00:14:14.920 --> 00:14:17.780
So let's see some of my notes on here.

00:14:17.780 --> 00:14:20.800
So you can save your SQL as a text file.

00:14:20.800 --> 00:14:24.960
So you don't have to have the string in your actual notebook, which is sometimes useful.

00:14:24.960 --> 00:14:33.300
And then when you import it in from that Pandas data frame, that's where a lot of people do their data cleaning and feature engineering and everything like that.

00:14:33.300 --> 00:14:37.120
So you could just pull in the raw data from SQL and do a lot of the data engineering there.

00:14:37.120 --> 00:14:41.400
Sometimes I do feature engineering in SQL and then pull it in.

00:14:41.400 --> 00:14:43.600
So that's kind of up to each user.

00:14:43.760 --> 00:14:48.200
But you really just set up the connection in your database using a package like SQLAlchemy.

00:14:48.200 --> 00:14:58.720
So you have a connection to the database and you pass your SQL string either directly or from the file and the database connection and it returns a Pandas data frame.

00:14:59.180 --> 00:15:02.920
So I'm happy to talk a little bit more about how I use this at work.

00:15:03.620 --> 00:15:04.700
I think this is really good.

00:15:04.700 --> 00:15:12.540
You know, one of the things to do with Pandas is there's just so many of these little functions that solve whole problems.

00:15:12.540 --> 00:15:23.240
You know, it's like, oh, you could go and use requests to download some HTML and you could use beautiful soup to do some selectors and you could get some stuff and parse out some HTML.

00:15:23.500 --> 00:15:27.520
And then you could get some table information out and then convert that into a data frame.

00:15:27.520 --> 00:15:32.020
Or you could just say, read HTML table bracket zero or whatever.

00:15:32.020 --> 00:15:35.460
And boom, you have it like knowing about these, I think is really interesting.

00:15:35.660 --> 00:15:37.120
So it's cool that you highlighted this one.

00:15:37.120 --> 00:15:40.960
I actually just, on a side, I'm just literally like in an hour.

00:15:40.960 --> 00:15:45.260
So probably before this show ships, we'll ship this episode I did with Vex.

00:15:45.260 --> 00:15:49.280
You have about 25 Panda functions you didn't even know existed.

00:15:49.540 --> 00:15:52.060
And what's interesting is like this one wasn't even on the list.

00:15:52.060 --> 00:15:54.260
So good.

00:15:54.260 --> 00:15:55.400
So I'll highlight another one.

00:15:55.400 --> 00:15:56.600
Now, you know, exists.

00:15:56.600 --> 00:15:58.080
That's pretty cool.

00:15:58.080 --> 00:16:00.720
Let's see a couple of comments from the audience.

00:16:00.720 --> 00:16:01.400
Sam Morley.

00:16:01.400 --> 00:16:03.320
Hey, Sam says, Pandas is so amazing.

00:16:03.320 --> 00:16:07.380
I always find something too late that it has all of these IO functions.

00:16:07.380 --> 00:16:10.760
And then we have Paul Ansel.

00:16:10.760 --> 00:16:16.720
Hey, Paul says, do you have any recommendations on tutorials for how to create good SQLAlchemy selectables?

00:16:16.720 --> 00:16:18.980
This always feels like the scariest bit.

00:16:19.360 --> 00:16:20.740
I don't have any of that on hand.

00:16:20.740 --> 00:16:25.180
I'll try to find something later or I'll ask my Twitter following and see what they recommend.

00:16:25.180 --> 00:16:27.480
I don't have a good list of tutorials for that one.

00:16:27.480 --> 00:16:32.060
I can talk about, yeah, by selectable, he said he means connectable.

00:16:32.060 --> 00:16:35.900
So, yeah, I don't have a tutorial for that.

00:16:35.900 --> 00:16:36.940
There's a lot of documentation.

00:16:36.940 --> 00:16:40.620
And I know that SQLAlchemy can be a little mysterious sometimes.

00:16:40.620 --> 00:16:42.000
Maybe that's why it's alchemy.

00:16:42.000 --> 00:16:46.760
But, yeah, I will try to share that later on Twitter.

00:16:46.760 --> 00:16:47.980
Yeah, fantastic.

00:16:47.980 --> 00:16:48.660
All right.

00:16:48.680 --> 00:16:51.580
And Paul says, read clipboard is pretty great.

00:16:51.580 --> 00:16:52.180
Yeah.

00:16:52.180 --> 00:16:53.320
Yeah, very cool.

00:16:53.320 --> 00:16:54.680
Bunch of different things there.

00:16:54.680 --> 00:16:55.180
Yeah.

00:16:55.180 --> 00:16:58.800
So if you want me to walk through an example of how I use this at work, I'm happy to do that.

00:16:58.800 --> 00:16:59.920
Yeah, give us a quick example.

00:16:59.920 --> 00:17:00.600
Yeah.

00:17:00.740 --> 00:17:06.540
So at Helio Campus, one thing we do is we connect to a lot of different databases at universities.

00:17:06.540 --> 00:17:12.240
So the universities will have separate databases for admissions, enrollment, financial aid.

00:17:12.240 --> 00:17:12.740
Those are all separate systems.

00:17:12.740 --> 00:17:14.240
Those are all separate systems.

00:17:14.240 --> 00:17:16.580
And so we pull all that data into a data warehouse.

00:17:16.580 --> 00:17:22.080
And in SQL, we can combine that data.

00:17:22.080 --> 00:17:26.080
And so we can either use this to just read one of those tables directly.

00:17:26.700 --> 00:17:36.580
Or we can combine what I typically do is do a little bit of cleanup and feature engineering and narrow down my data set to the population that I want to run through my model in SQL.

00:17:36.580 --> 00:17:38.700
And then just pull those final results.

00:17:38.700 --> 00:17:41.980
And now I've got my data set with at least preliminary features.

00:17:41.980 --> 00:17:44.840
I might do some standardization and things like that in pandas.

00:17:44.840 --> 00:17:52.040
But I've got a pretty clean and subset of the data that I need right into my Jupyter notebook.

00:17:52.040 --> 00:17:53.160
Oh, that's fantastic.

00:17:53.160 --> 00:17:53.900
Pretty great.

00:17:53.900 --> 00:17:58.860
Yeah, I think definitely understanding SQL is an important skill for data scientists.

00:17:58.860 --> 00:18:04.500
And it's slightly different than for, say, like a web API developer, right?

00:18:04.500 --> 00:18:04.840
Absolutely.

00:18:04.840 --> 00:18:05.980
That's why I wrote the book.

00:18:05.980 --> 00:18:06.960
That's awesome.

00:18:06.960 --> 00:18:07.540
Yeah, for sure.

00:18:07.540 --> 00:18:10.780
So on the API side, you kind of get something set up.

00:18:10.780 --> 00:18:15.120
You're very likely using an ORM like SQLAlchemy and you just connect it and go.

00:18:15.120 --> 00:18:18.500
And once you get it set, you can kind of forget about it and just program against it.

00:18:18.500 --> 00:18:20.440
As a data scientist, you're exploring.

00:18:20.440 --> 00:18:21.700
You don't totally know, right?

00:18:21.700 --> 00:18:25.900
You're kind of out there testing and digging into stuff and sorting and filtering.

00:18:25.900 --> 00:18:33.960
And yeah, I would say you probably need a better fluency with SQL as a data scientist a lot of times than as a web developer.

00:18:33.960 --> 00:18:37.620
Because I can just use an ORM or ODM and just kind of know what it's doing.

00:18:37.740 --> 00:18:45.220
Yeah, and it enables you to build your own data sets instead of relying on a data engineering trying to explain what you need and why and which fields you need.

00:18:45.220 --> 00:18:48.180
Now you could just do it yourself or, you know, add a field if you need it.

00:18:48.180 --> 00:18:51.600
You can do more sophisticated things like window functions.

00:18:51.600 --> 00:18:59.720
So yeah, I think knowing SQL is really a value add if you're looking to become a data scientist and, you know, putting yourself out there on the market.

00:18:59.720 --> 00:19:03.620
If you can do the whole pipeline end to end, it definitely makes you stand out.

00:19:03.620 --> 00:19:04.540
I would think so.

00:19:04.540 --> 00:19:05.140
All right.

00:19:05.140 --> 00:19:06.720
One thing to wrap up on this.

00:19:06.720 --> 00:19:10.780
Sam asks, can you configure SQLAlchemy to dump the raw queries that it runs?

00:19:10.780 --> 00:19:11.540
Yes.

00:19:11.540 --> 00:19:12.300
Yeah.

00:19:12.540 --> 00:19:15.980
In this case, you have the raw query in your function call.

00:19:15.980 --> 00:19:20.620
So I'm not actually using SQLAlchemy for that because I'm providing a query.

00:19:20.620 --> 00:19:21.340
Yeah, you just got a select statement, right?

00:19:21.340 --> 00:19:28.660
The problem with SQLAlchemy and data science is you have to, the structure of your models has to exactly match the structure of the data.

00:19:28.760 --> 00:19:35.040
And often I imagine you're just kind of dealing with the loose data and it doesn't make sense to take the time to like model it in classes.

00:19:35.040 --> 00:19:38.560
But for SQLAlchemy, you can just set echo equal true when you create the engine.

00:19:38.560 --> 00:19:46.000
And then everything that would get sent to the crossover to the database gets echoed as like DDL or SQL or whatever that it does.

00:19:46.000 --> 00:19:46.700
So yes.

00:19:46.700 --> 00:19:47.220
Good.

00:19:47.220 --> 00:19:47.380
Cool.

00:19:47.380 --> 00:19:47.820
For sure.

00:19:47.820 --> 00:19:48.360
Yeah.

00:19:48.360 --> 00:19:49.060
All right.

00:19:49.060 --> 00:19:51.660
Brian, want to tell us about our sponsor?

00:19:51.660 --> 00:19:52.820
Yeah, let's.

00:19:52.820 --> 00:19:57.940
I am pleased and happy that Shortcut is sponsoring the episode.

00:19:58.280 --> 00:19:59.120
So thank you, Shortcut.

00:19:59.120 --> 00:20:01.880
Formerly Clubhouse for sponsoring the episode.

00:20:01.880 --> 00:20:06.640
There are a lot of project management tools out there, but most suffer from common problems.

00:20:06.640 --> 00:20:14.160
Like it's too simple for an engineering team to use on several projects or it's too complex and it's hard to get started.

00:20:14.160 --> 00:20:16.080
And there's tons of options.

00:20:16.080 --> 00:20:19.360
And some of them are great for managers, but bad for engineers.

00:20:19.360 --> 00:20:21.440
And some are great for engineers and bad for managers.

00:20:21.440 --> 00:20:22.860
Shortcut is different.

00:20:22.860 --> 00:20:27.800
It's built for software teams and based on making workflows super easy.

00:20:27.800 --> 00:20:30.680
For example, keyboard friendly user interface.

00:20:30.680 --> 00:20:33.660
The UI is intuitive for mouse lovers, of course.

00:20:33.660 --> 00:20:39.860
But the activities that you use every day can be set to keyboard shortcuts if they aren't already.

00:20:39.860 --> 00:20:43.680
Just learn them and you'll start working faster.

00:20:43.680 --> 00:20:44.220
It's awesome.

00:20:44.220 --> 00:20:46.060
Tight VCS integration.

00:20:46.060 --> 00:20:49.940
So you can update task progress and commits with a commit or a PR.

00:20:49.940 --> 00:20:50.860
That's sweet.

00:20:51.300 --> 00:20:53.200
And iteration planning is a breeze.

00:20:53.200 --> 00:20:57.320
I like that there's a burndown and cycle time charts built in.

00:20:57.320 --> 00:21:00.560
They just are set up already for you when you start using this.

00:21:00.560 --> 00:21:02.360
So it's a pretty clean system.

00:21:02.360 --> 00:21:05.640
Give it a try at shortcut.com slash Python bytes.

00:21:05.640 --> 00:21:06.600
Yeah, absolutely.

00:21:06.600 --> 00:21:07.360
Thanks, Shortcut.

00:21:07.360 --> 00:21:08.540
For sponsoring this episode.

00:21:09.100 --> 00:21:10.880
Now, what have we got next here?

00:21:10.880 --> 00:21:11.720
Pidgin.

00:21:11.720 --> 00:21:12.700
I want to talk about Pidgin.

00:21:12.700 --> 00:21:17.940
So we already talked about Will McGooghan and Rich.

00:21:17.940 --> 00:21:24.000
So it's time to talk about Anthony Shaw so that we can complete our shoutouts we always seem to give over on the podcast.

00:21:24.480 --> 00:21:27.920
So I want to talk about Pidgin because I just interviewed Anthony Shaw.

00:21:27.920 --> 00:21:32.140
But more importantly, he just released Pidgin as 1.0.

00:21:32.140 --> 00:21:36.440
So Pidgin is a drop-in JIT compiler for Python 3.10.

00:21:36.440 --> 00:21:37.460
Let me say that again.

00:21:37.460 --> 00:21:39.800
A JIT compiler for Python.

00:21:39.800 --> 00:21:48.900
And there have been other speed-up type of attempts where people will like fork CPython and they'll do something inside of it to make it different.

00:21:48.900 --> 00:21:49.800
Think Cinder.

00:21:49.920 --> 00:21:55.180
There have been attempts to create a totally different but compatible one like PyPy.

00:21:55.180 --> 00:22:02.240
And they've worked pretty well, but they always have some sort of incompatibility or something.

00:22:02.240 --> 00:22:07.780
It would be nice if just the Python you ran could be compiled to go faster if you want it to be.

00:22:07.780 --> 00:22:09.280
So that's what this is.

00:22:09.280 --> 00:22:17.100
It uses a PEP whose number I forgot that allows you to plug in something that inspects the method frames before they get executed.

00:22:17.100 --> 00:22:26.580
And then instead of just interpreting that code, the bytecode as Python bytecode, it'll actually compile it to machine instructions.

00:22:26.580 --> 00:22:30.680
First to .NET intermediate instructions, intermediate language.

00:22:30.680 --> 00:22:34.880
And then those get JIT compiled to machine instructions that then run directly.

00:22:34.880 --> 00:22:39.440
Works on Linux, macOS, Windows, X64, and ARM64.

00:22:39.440 --> 00:22:42.260
So this is a pretty cool development.

00:22:42.740 --> 00:22:43.700
It is pretty cool.

00:22:43.700 --> 00:22:44.200
Yeah.

00:22:44.200 --> 00:22:49.860
So if we go over here and check it out, in order to use it, it has some requirements.

00:22:49.860 --> 00:22:50.960
You just pip install Pigeon.

00:22:50.960 --> 00:22:51.680
That's it.

00:22:51.680 --> 00:22:52.540
That's crazy, right?

00:22:52.540 --> 00:22:54.400
And then it has to be on 310.

00:22:54.400 --> 00:22:55.340
It can't be older than that.

00:22:55.340 --> 00:22:58.000
And you have to have .NET 6 installed.

00:22:58.000 --> 00:22:58.540
Okay.

00:22:58.540 --> 00:22:59.960
So that just got released.

00:22:59.960 --> 00:23:02.240
It's a good chance you don't have .NET 6 installed.

00:23:02.640 --> 00:23:07.780
But then once you set it up right, you can just say import Pigeon, Pigeon.enable at the startup of your app.

00:23:07.780 --> 00:23:10.800
And then it will look at all the methods and JIT compile them.

00:23:11.460 --> 00:23:16.600
So if you come down here, like he has an example of a half function that Anthony put up here.

00:23:16.600 --> 00:23:20.060
And when it first loads, it's not JIT compiled.

00:23:20.060 --> 00:23:24.580
But after that, you can go and say, if you run it, you can say disassemble this thing.

00:23:24.580 --> 00:23:31.060
And it'll show you basically assembly instructions of what would have otherwise been Python code.

00:23:31.060 --> 00:23:31.560
Wow.

00:23:31.560 --> 00:23:32.440
It's wild, right?

00:23:32.640 --> 00:23:34.560
So it's a little bit like Numba.

00:23:34.560 --> 00:23:44.600
It's a little bit like a tiny bit like Cython in the sense that it takes Python code, translates it into something else that then can be interoperated with, and then makes it go fast.

00:23:44.600 --> 00:23:46.400
So this is all well and good.

00:23:46.400 --> 00:23:49.800
If you're going to use it on the web, by default, it would be just fine.

00:23:49.800 --> 00:23:57.560
Except if you're hosting it, normally you host it in this supervisor process and then a bunch of forked off processes.

00:23:57.960 --> 00:24:02.920
So there's a WSGI app configuration thing you can do as well somewhere in the docs.

00:24:02.920 --> 00:24:03.700
I'm not seeing it right now.

00:24:03.700 --> 00:24:11.300
But you basically allow it to push the pigeon changes on down into the worker processes, which is pretty cool.

00:24:11.300 --> 00:24:19.300
And it has a bunch of comparisons against PyPy, Piston, Numba, IronPython, et cetera, Nutka, and so on.

00:24:19.300 --> 00:24:21.200
Now, it's not that much faster.

00:24:21.200 --> 00:24:29.800
It is faster when you're doing more data science-y things, I believe, than if you're doing just a query against a database where you're mostly just waiting anyway.

00:24:29.800 --> 00:24:33.240
But still, I think this is promising, and it's really pretty early days.

00:24:33.240 --> 00:24:40.380
So the thing to look at is if there's optimizations coming along here somewhere in the docs.

00:24:41.220 --> 00:24:50.260
Anthony lists out the various optimizations he's put in so far, and really, it just needs more optimizations to make it faster still, which is pretty neat.

00:24:50.260 --> 00:24:51.300
I think that's pretty cool.

00:24:51.300 --> 00:24:57.740
One of the things that my first reaction was, oh, it's .NET only, so I have to use it on Windows.

00:24:57.740 --> 00:25:00.880
But that's not been that way for a long time.

00:25:00.880 --> 00:25:02.540
So .NET runs on just about everything.

00:25:02.540 --> 00:25:03.360
Yeah, exactly.

00:25:03.460 --> 00:25:05.120
It supports all the different frameworks.

00:25:05.120 --> 00:25:11.660
There's even this thing called live.trypigeon.com, where you can write Python code, like, over here on the left.

00:25:11.660 --> 00:25:13.580
And then you can say, compile it.

00:25:13.580 --> 00:25:17.860
And it will actually show you the assembly that it would compile to.

00:25:17.860 --> 00:25:20.740
And then here's the .NET intermediate language.

00:25:20.740 --> 00:25:23.880
I guess maybe they should possibly be switching orders here.

00:25:23.880 --> 00:25:27.080
Like, first it goes to IL, and then it goes to machine instructions through the JIT.

00:25:27.080 --> 00:25:31.140
But it shows you all the stuff that it's doing to make this work.

00:25:31.160 --> 00:25:35.980
You could even see at the bottom, there's, like, sort of a visual understanding of what it's doing.

00:25:35.980 --> 00:25:40.580
One of the things that's really cool that it does is, imagine you've got a math problem up here.

00:25:40.580 --> 00:25:47.940
Like, you're saying, like, X equals Y times Y plus Z times Z, or, you know, something like that.

00:25:47.940 --> 00:25:51.160
Like, each one of those steps generates an intermediate number.

00:25:51.160 --> 00:25:56.880
So, for example, Z times Z would generate, by default, a Python number.

00:25:56.880 --> 00:26:00.720
And then so would Y times Y, and then the addition, and finally you assign it.

00:26:01.120 --> 00:26:07.680
What it'll do is it'll say, well, okay, if those are two floats, let's just store that as a C float in the intermediate computation.

00:26:07.680 --> 00:26:09.200
And then that's as a C float.

00:26:09.200 --> 00:26:13.740
And so it can sort of stay lower level as it's doing a lot of computational type of things.

00:26:13.740 --> 00:26:15.580
So there's a bunch of interesting optimizations.

00:26:15.580 --> 00:26:16.580
People can check this out.

00:26:16.580 --> 00:26:17.980
I haven't had a chance to try it yet.

00:26:17.980 --> 00:26:19.640
I was hoping to, but haven't got there yet.

00:26:19.640 --> 00:26:22.120
Yeah, really interesting conversation you had with him, too.

00:26:22.120 --> 00:26:23.040
Oh, thanks.

00:26:24.040 --> 00:26:33.540
It's interesting timing to just get him to jump on this, like, right after he wrote the book on Python internals, CPython internals, to jump into this.

00:26:33.540 --> 00:26:36.840
Well, I guess he's working on it before, but still.

00:26:36.840 --> 00:26:40.120
Yeah, you definitely got to know CPython internals to do this.

00:26:40.120 --> 00:26:48.060
Renee, do you guys do anything to optimize your code with, like, Numba or Cython or anything like that?

00:26:48.060 --> 00:26:51.120
Or are you just running straight Python and letting the libraries deal with it?

00:26:51.120 --> 00:26:52.200
Yeah, not currently.

00:26:52.200 --> 00:26:59.340
We have a pretty good server and are working with relatively small data sets, you know, not millions of rows, for example.

00:26:59.340 --> 00:27:03.100
So for right now, we haven't gone in this direction at all.

00:27:03.100 --> 00:27:11.060
I can imagine this would also be really useful if you were a computer science student and trying to understand what's going on under the hood when you run these things.

00:27:11.060 --> 00:27:21.020
So it's interesting that it's for the people that aren't seeing the visual, you kind of have three columns here with the code side by side to kind of get a peek under the hood at what's going on there.

00:27:21.020 --> 00:27:23.180
But no, this isn't something I've used personally.

00:27:23.180 --> 00:27:24.080
Yeah, yeah.

00:27:24.080 --> 00:27:25.360
I haven't used it either.

00:27:25.360 --> 00:27:26.640
But like I said, I would like to.

00:27:26.640 --> 00:27:30.380
I think it's got the ability to just plug in and make things faster.

00:27:30.380 --> 00:27:33.100
And really, it is faster to some degree.

00:27:33.100 --> 00:27:35.040
Sometimes I think it's slower, sometimes faster.

00:27:35.040 --> 00:27:40.660
But the more optimizations the JIT compiler gets, the better it could be, right?

00:27:40.760 --> 00:27:54.340
So like if it could inline function calls rather than calling them, or it could, there's things like if it sees you allocating a list and putting stuff into it, it can skip some intermediate steps and just straight allocate that.

00:27:54.340 --> 00:28:01.160
Or if you're doing accessing elements by index out of the list, it can just do pointer operations instead of going through the Python APIs.

00:28:01.160 --> 00:28:06.700
There's a lot of hard work that Anthony's put into this, and I think it's pretty cool.

00:28:06.700 --> 00:28:08.400
Yeah, I haven't tried it.

00:28:08.400 --> 00:28:08.940
I would like to.

00:28:08.940 --> 00:28:10.000
Yeah.

00:28:10.360 --> 00:28:10.560
Cool.

00:28:10.560 --> 00:28:11.200
Indeed.

00:28:11.200 --> 00:28:11.580
All right.

00:28:11.580 --> 00:28:12.880
Brian, what do you got for us?

00:28:12.880 --> 00:28:18.120
Well, actually, before I jump to the next topic, I wanted to mention, I wanted to shoo into this last conversation.

00:28:18.120 --> 00:28:27.580
Brett Cannon just wrote an interesting article called Selecting a Programming Language Can Be a Form of Premature Optimization.

00:28:27.720 --> 00:28:32.940
And this is relevant to the conversation.

00:28:32.940 --> 00:28:40.620
He says if you think Python might be too slow, another implementation like Pigeon is like step three.

00:28:40.620 --> 00:28:47.680
So first prototype in Python, then optimize your data structures and algorithms and also profile it.

00:28:47.680 --> 00:28:52.260
And then try another implementation before you abandon Python altogether.

00:28:52.880 --> 00:28:59.380
And then, you know, you can do some late bindings like language bindings to connect to see if you need to or rust.

00:29:00.100 --> 00:29:07.440
But I think it ties in as like when would I choose Pigeon or PyPI over CPython.

00:29:07.440 --> 00:29:10.580
Well, it's step three, just to let people know.

00:29:10.580 --> 00:29:12.380
Step three.

00:29:12.380 --> 00:29:12.800
Got it.

00:29:12.800 --> 00:29:13.540
Step three.

00:29:13.940 --> 00:29:17.540
I wanted to do something more lighthearted, like use print for debugging.

00:29:17.540 --> 00:29:20.180
So I love this article.

00:29:20.180 --> 00:29:22.120
I am guilty of this.

00:29:22.120 --> 00:29:26.060
Of course, I use debuggers and logging systems as well.

00:29:26.060 --> 00:29:29.580
But I also throw print statements in there sometimes.

00:29:29.580 --> 00:29:31.700
And I'm not ashamed to say it.

00:29:31.700 --> 00:29:35.460
So Adam Johnson wrote tips for debugging the print.

00:29:35.460 --> 00:29:38.980
And there were a couple that with print, there were a couple that stood out to me.

00:29:38.980 --> 00:29:41.420
I really wanted to mention because I use them a lot.

00:29:41.780 --> 00:29:48.480
Even with logging, though, is use debug variables with f-strings and the equal sign.

00:29:48.480 --> 00:29:51.100
So this is brilliant.

00:29:51.100 --> 00:29:52.300
It's been in since 3.8.

00:29:52.300 --> 00:29:59.780
Instead of typing like print widget equals and then in a string and then the widget number or something,

00:29:59.780 --> 00:30:05.260
you can just use the f-strings and do the equal sign and it interpolates for you.

00:30:05.260 --> 00:30:06.720
Or it doesn't interpolate.

00:30:06.720 --> 00:30:07.980
It just prints it for you.

00:30:07.980 --> 00:30:08.700
So it's nice.

00:30:08.700 --> 00:30:09.460
I like that.

00:30:10.160 --> 00:30:12.700
The next one is, I love this.

00:30:12.700 --> 00:30:13.620
Use emojis.

00:30:13.620 --> 00:30:14.960
I never thought to do this.

00:30:14.960 --> 00:30:15.640
This is brilliant.

00:30:15.640 --> 00:30:22.380
Throw emojis in your print statement so they pop out when you're debugging.

00:30:22.380 --> 00:30:23.260
Have you ever used emojis?

00:30:23.260 --> 00:30:23.400
Yeah, this is cool.

00:30:23.400 --> 00:30:26.180
I started using emojis in comments.

00:30:26.180 --> 00:30:27.080
Oh, okay.

00:30:27.080 --> 00:30:27.940
Comments.

00:30:27.940 --> 00:30:28.300
Nice.

00:30:28.680 --> 00:30:28.940
Yeah.

00:30:28.940 --> 00:30:33.120
So I'll put like the different emojis mean for me, I was doing some API stuff.

00:30:33.120 --> 00:30:35.780
So like, oh, this is the read only method here of an API.

00:30:35.780 --> 00:30:37.500
So I'll put a certain emoji up there.

00:30:37.500 --> 00:30:39.000
And this is one that changes data.

00:30:39.000 --> 00:30:40.540
So here's what I'll put there.

00:30:40.540 --> 00:30:42.860
Here's one that returns a list versus a single thing.

00:30:42.860 --> 00:30:45.320
So I'll put a whole bunch of those emojis and stuff.

00:30:45.600 --> 00:30:45.840
Yeah.

00:30:45.840 --> 00:30:49.520
Well, I mean, I used to do like a whole bunch of plus signs because they're easy to see.

00:30:49.520 --> 00:30:52.100
But an emoji would be way easier to see.

00:30:52.100 --> 00:30:53.100
Way more fun, man.

00:30:53.100 --> 00:30:53.740
Yeah.

00:30:53.740 --> 00:30:54.040
Yeah.

00:30:54.040 --> 00:30:55.120
I do this as well.

00:30:55.220 --> 00:30:59.640
I print all the time for debugging, especially in Jupyter Notebooks, because you don't always

00:30:59.640 --> 00:31:01.900
have the most sophisticated debugging tools in there.

00:31:01.900 --> 00:31:06.720
But being able to print and see what's going on as you go through each step of the notebook.

00:31:06.720 --> 00:31:11.100
And emojis are a great idea for that because it's so visual as you're scrolling through.

00:31:11.100 --> 00:31:14.120
You want to like they're showing there the X and the checkmark emoji.

00:31:14.120 --> 00:31:18.020
I like that for my little to-do lists in the comments that I leave for myself.

00:31:18.020 --> 00:31:18.120
Yeah, yeah.

00:31:18.120 --> 00:31:18.700
I have a thought.

00:31:18.700 --> 00:31:19.700
So I've done that as well.

00:31:19.700 --> 00:31:20.060
That's cool.

00:31:20.220 --> 00:31:26.780
Chris May out in the audience just put a heart sign, smiley face emoji as a response

00:31:26.780 --> 00:31:27.160
to this.

00:31:27.160 --> 00:31:27.640
Love it.

00:31:27.640 --> 00:31:28.380
Last thing.

00:31:28.380 --> 00:31:30.060
He's got like seven tips.

00:31:30.060 --> 00:31:36.280
The last tip I wanted to talk about was using rich and or specifically rich print, rich.print

00:31:36.280 --> 00:31:38.580
or pprint.

00:31:38.580 --> 00:31:44.920
So for pprint, you have to do from pprint install pprint or something, or unless you wanted to

00:31:44.920 --> 00:31:46.880
say it twice with pprint dot pprint.

00:31:46.880 --> 00:31:50.200
But pprint stands for pretty printing.

00:31:50.200 --> 00:31:55.100
And the gist of this really is the structures by default print horribly.

00:31:55.100 --> 00:32:00.660
If you just print like a dict or a set or something, it looks gross.

00:32:00.660 --> 00:32:04.080
But rich and pretty print make it look nice.

00:32:04.080 --> 00:32:07.200
So if you're debugging, printing with those and debugging, use that.

00:32:07.200 --> 00:32:08.500
So yeah.

00:32:08.500 --> 00:32:12.120
There's also exception handling stuff in there for it.

00:32:12.120 --> 00:32:14.520
And there's a lot of that kind of debugging stuff in rich.

00:32:14.520 --> 00:32:15.520
Yeah.

00:32:15.520 --> 00:32:17.300
Printing exceptions is great with that.

00:32:17.300 --> 00:32:22.880
I also wanted to say one of the reasons why I, one of the places where I use printing

00:32:22.880 --> 00:32:29.740
a lot for debugging is I print to print stuff in my, what I expect is going on when I'm

00:32:29.740 --> 00:32:30.600
writing a test function.

00:32:30.600 --> 00:32:33.340
So I'll often print out the flow, what's going on.

00:32:33.340 --> 00:32:40.380
The reason I do that is when if pytest for pytest, if a test fails, pytest dumps the standard

00:32:40.380 --> 00:32:40.620
out.

00:32:40.620 --> 00:32:43.940
So it'll dump all of your print statements from the failed procedure.

00:32:44.180 --> 00:32:47.260
So that's either the test under code or the test itself.

00:32:47.260 --> 00:32:49.360
If there's print statements, it gets dumped out.

00:32:49.360 --> 00:32:50.240
So that's helpful.

00:32:50.240 --> 00:32:50.800
Yeah.

00:32:50.800 --> 00:32:51.600
Nice.

00:32:51.600 --> 00:32:52.320
That's great.

00:32:52.320 --> 00:32:52.840
I love it.

00:32:52.840 --> 00:32:54.020
I use print statements a lot.

00:32:54.020 --> 00:32:55.780
My output is very verbose.

00:32:55.780 --> 00:32:58.600
You can see right in order what's happening.

00:32:58.600 --> 00:33:02.380
Sometimes a debugger helps, but sometimes it's time to just print.

00:33:02.380 --> 00:33:02.900
Yeah.

00:33:03.200 --> 00:33:05.680
Speaking of visual stuff, what's your last one here, Renee?

00:33:05.680 --> 00:33:06.300
Yeah.

00:33:06.300 --> 00:33:12.460
So in our line of work with data science, especially when you're providing the models as tools for

00:33:12.460 --> 00:33:17.780
end users that aren't the data scientists themselves, you really want the explainability is really

00:33:17.780 --> 00:33:18.120
important.

00:33:18.120 --> 00:33:23.980
So being able to explain why a certain prediction got the value it did, what the different inputs

00:33:23.980 --> 00:33:27.980
are, we're always working to make that more transparent for our end users.

00:33:28.440 --> 00:33:35.500
In our case, for example, we might be predicting which students might be at risk of not retaining

00:33:35.500 --> 00:33:36.260
at the university.

00:33:36.260 --> 00:33:37.880
So not being enrolled a year later.

00:33:37.880 --> 00:33:43.080
So what are the different factors, both overall for the whole population that are correlated with

00:33:43.080 --> 00:33:44.680
not being enrolled for a year?

00:33:44.680 --> 00:33:49.580
And for each individual student, what might be particular factors that, at least from the

00:33:49.580 --> 00:33:52.720
model's perspective, puts them at higher risk?

00:33:52.900 --> 00:33:59.600
So this package is called SHAP, and that stands for Shapley Additive Explanations.

00:33:59.600 --> 00:34:04.060
It was brought to my attention by my team member, Brian Richards at Helio Campus.

00:34:04.060 --> 00:34:08.060
And now we use it very frequently because it has really good visualizations.

00:34:08.060 --> 00:34:12.560
So these Shapley values, apparently they're from game theory.

00:34:12.560 --> 00:34:15.140
I won't pretend to understand the details of how they're generated.

00:34:15.680 --> 00:34:18.920
But you could think of it as like a model on top of your model.

00:34:18.920 --> 00:34:22.240
So it's additive and all the different features.

00:34:22.240 --> 00:34:26.400
If you see the visualization here, it's showing kind of a little waterfall chart.

00:34:26.400 --> 00:34:31.980
So some of the values that you think of a particular row that you're running through your algorithm,

00:34:31.980 --> 00:34:37.380
some of the values in that row are going to make the, if you're doing a classification model,

00:34:37.380 --> 00:34:40.120
some values might make you more likely to be in one class.

00:34:40.120 --> 00:34:42.740
Some might make you more likely to be in the other class.

00:34:42.740 --> 00:34:46.080
So you have these visuals of kind of the push and pull of each value.

00:34:46.080 --> 00:34:50.820
In this visual, we're seeing, you know, age is pushing a number to the right.

00:34:50.820 --> 00:34:52.760
Sex is pushing it to the left.

00:34:52.760 --> 00:34:55.980
I guess BP and BMI, that looks like a blood pressure.

00:34:55.980 --> 00:34:58.120
So it's got this like waterfall type of chart.

00:34:58.120 --> 00:35:00.340
And what it's actually doing is it's comparing.

00:35:00.340 --> 00:35:03.700
It starts with the expected value for the whole population.

00:35:03.700 --> 00:35:11.420
And then it's showing you where this particular record, each of the input values is kind of nudging

00:35:11.420 --> 00:35:15.360
that eventual prediction one in one direction or the other.

00:35:15.360 --> 00:35:22.480
So it's just nice visually to have those waterfall plots and to see which features are negatively

00:35:22.480 --> 00:35:27.420
or positively correlated with the, you know, the end result.

00:35:27.740 --> 00:35:30.340
And you could also do some cool scatter plots with this.

00:35:30.340 --> 00:35:37.280
So you can do the input value versus the shop value and have a point for every item in your population.

00:35:37.280 --> 00:35:39.500
So for in our, in our example, that would be students.

00:35:39.500 --> 00:35:45.200
So we can have a scatter plot of all the students and something like the number of cumulative credits

00:35:45.200 --> 00:35:46.760
that they have as of that term.

00:35:46.900 --> 00:35:52.640
And so you'll see the gradient of like from low credits to high credits, it's not usually linear.

00:35:52.640 --> 00:35:54.860
What are those kind of break points?

00:35:54.860 --> 00:36:02.840
And at what point are the values, you know, positively impactful to likelihood to retain or in the opposite direction?

00:36:02.840 --> 00:36:11.320
Of course, I'm glad that they put in this documentation, a whole, they have a whole section on basically correlation is not causation.

00:36:11.580 --> 00:36:14.740
And we're constantly having to talk to our end users about that.

00:36:14.740 --> 00:36:23.000
But, you know, if we say a student that, you know, lives in a certain town is, you know, potentially more likely to retain,

00:36:23.000 --> 00:36:28.840
maybe because of distance from campus, or maybe you have traditionally recruit a lot of students from that town.

00:36:28.840 --> 00:36:33.740
It doesn't mean that if you force someone to move to that town, they're more likely to stay at your institution.

00:36:33.740 --> 00:36:34.040
Right.

00:36:34.720 --> 00:36:37.600
So, yeah, correlation is not causation.

00:36:37.600 --> 00:36:44.920
And I'm going to switch over here to the visual, to something called a B-swarm plot that you can output this right in your Jupyter notebook,

00:36:44.920 --> 00:36:48.040
which is really handy when you develop a new model.

00:36:48.040 --> 00:36:51.940
And I'll try to describe this for people who are listening to the audio.

00:36:51.940 --> 00:36:55.760
It has along the x-axis a list of features.

00:36:56.400 --> 00:37:01.780
So you've got in this example on their website, age, relationship, capital gain, marital status.

00:37:01.780 --> 00:37:05.160
And then you see a bunch of dots going across horizontally.

00:37:05.160 --> 00:37:08.220
And there's areas where there's little clusters of dots.

00:37:08.220 --> 00:37:12.600
So what this is showing is the x-axis is the SHAP value.

00:37:12.600 --> 00:37:15.580
So what this SHAP package outputs.

00:37:16.100 --> 00:37:22.460
So you can see visually across, you know, what is the spread of the impact of this value.

00:37:22.460 --> 00:37:32.240
So if each dot is a person in this case, you see people all the way to the right, whatever their age was, positively impacted their eventual score.

00:37:32.240 --> 00:37:35.940
People all the way to the left, their age negatively impacted the score.

00:37:35.940 --> 00:37:39.700
And then each dot is a color that ranges from blue to red.

00:37:39.700 --> 00:37:45.420
So the blue ones are people with low age and the red ones are people with a high age.

00:37:45.480 --> 00:37:52.080
So you can see here, basically the higher the age, the more positive their eventual prediction.

00:37:52.080 --> 00:38:00.420
So just an interesting way to get both like a feature importance and see the distribution of the values within each feature.

00:38:00.420 --> 00:38:08.040
So it's just really helpful when you're doing predictive models, both for evaluating your own model and then eventually explaining it to end users.

00:38:09.000 --> 00:38:16.720
So would a wider spread mean that the feature is more useful or does it have any?

00:38:16.720 --> 00:38:17.260
Yeah.

00:38:17.260 --> 00:38:19.760
Especially if you can see a split in the numbers there.

00:38:19.760 --> 00:38:25.520
So you see in this example, relationship, you've got all the red ones to the right and all the blue ones to the left.

00:38:25.600 --> 00:38:31.820
That means that there's a clear relationship from this relationship field with the target variable.

00:38:31.820 --> 00:38:37.680
So there's a clear split where the low values are on one side and the high values are on the other side.

00:38:37.680 --> 00:38:48.060
And then, yeah, the spread means that if there's not a good example here, but sometimes you'll see like two clumps, two bee swarms spread apart with nothing in the middle.

00:38:48.520 --> 00:38:53.940
So that's when you have a really clear spread of the high impact group versus the low impact group.

00:38:53.940 --> 00:38:58.560
And if it's more narrow, that's less of an important variable.

00:38:58.560 --> 00:39:11.360
So you see if you look at the one that's sorted by max, here we go, absolute value of the shop value, the ones near the bottom for the population have less impact.

00:39:11.360 --> 00:39:18.260
Now there might be one person in there where that particular value was like the deciding factor of which class they ended up in.

00:39:18.260 --> 00:39:25.660
But for the population as a whole, there's less differentiation across these values than across the ones near the top of the list.

00:39:25.660 --> 00:39:30.220
Yeah, this is cool because that visualization of models is very tricky, right?

00:39:30.220 --> 00:39:32.780
And it's something like knowing why you got an answer.

00:39:32.780 --> 00:39:34.080
Now this looks very helpful.

00:39:34.080 --> 00:39:35.480
Yeah, it's really useful.

00:39:35.480 --> 00:39:40.260
And the visuals are so pretty by default, but then you can also pull those values into other tools.

00:39:40.260 --> 00:39:46.260
So for each feature in each row, you get a shop value.

00:39:46.260 --> 00:39:50.000
So you can write those back to the database and then pull those values into another tool.

00:39:50.000 --> 00:40:06.340
Like we use it in Tableau to highlight for each student, what are those really important features, either making them, you know, if you're not making them, it's not causation, but, you know, correlated with their more likely to retain or less likely to retain.

00:40:06.340 --> 00:40:10.160
So we might say, well, for this student, their GPA, that's the main factor.

00:40:10.160 --> 00:40:11.500
Their GPA is really low.

00:40:11.500 --> 00:40:13.840
Students with low GPAs tend not to retain.

00:40:13.840 --> 00:40:22.560
And so when the end user is looking at all of their values in, you know, in a table or some other kind of view, you can use the shop value to highlight.

00:40:22.560 --> 00:40:24.560
The GPA is the one you need to hone in on.

00:40:24.560 --> 00:40:26.280
The student is struggling academically.

00:40:26.420 --> 00:40:26.540
Right.

00:40:26.540 --> 00:40:28.980
Try to help them get some help with grades, for example.

00:40:28.980 --> 00:40:29.560
Cool.

00:40:29.560 --> 00:40:30.840
Yeah, this is a great find.

00:40:30.840 --> 00:40:31.360
Indeed.

00:40:31.360 --> 00:40:31.960
Indeed.

00:40:31.960 --> 00:40:32.320
Indeed.

00:40:32.320 --> 00:40:34.680
Brian, that brings us to the extras.

00:40:34.680 --> 00:40:35.520
Extras.

00:40:35.520 --> 00:40:36.460
Got any extras for us?

00:40:36.460 --> 00:40:37.140
I do.

00:40:37.140 --> 00:40:40.660
I've got one that was just a quick one.

00:40:40.660 --> 00:40:41.240
Let's see.

00:40:41.240 --> 00:40:42.060
Pull it up.

00:40:43.020 --> 00:40:49.080
Matthew Feigert mentioned on Twitter that pip index is a cool thing.

00:40:49.080 --> 00:40:52.240
And I kind of didn't know about it.

00:40:52.240 --> 00:40:53.020
So this is neat.

00:40:53.020 --> 00:40:57.440
So pip index is something you can take pip index.

00:40:57.440 --> 00:41:00.080
Well, specifically pip index versions.

00:41:00.080 --> 00:41:02.440
So pip index does a whole bunch of stuff.

00:41:02.660 --> 00:41:16.860
pip index versions will tell you, if you also give it a package, it'll tell you all the different versions that are available on PyPI and which one you have and which, you know, if you're out of date and stuff.

00:41:16.860 --> 00:41:25.800
But so for instance, if you're thinking about upgrading something and you don't know what to upgrade to, you can look to see what all is there, I guess.

00:41:25.800 --> 00:41:27.260
Or you want to roll it back.

00:41:27.260 --> 00:41:28.880
You're like, oh, this version is not working.

00:41:28.880 --> 00:41:30.880
I want to go back to a lower one.

00:41:31.020 --> 00:41:34.140
But if you're on 2.0, is it not 1.0?

00:41:34.140 --> 00:41:34.720
What is it, right?

00:41:34.720 --> 00:41:35.380
What do you go back to?

00:41:35.380 --> 00:41:37.500
And so this will like list all the available versions.

00:41:37.500 --> 00:41:44.140
Basically, this is a CLI version of the releases option in pypi.org.

00:41:44.140 --> 00:41:44.640
Right.

00:41:44.640 --> 00:41:48.380
And that's not like obvious how to get to on PyPI.

00:41:48.380 --> 00:41:50.780
But I know you can get to it.

00:41:50.780 --> 00:41:51.720
You can see all the releases.

00:41:51.720 --> 00:41:53.860
But by default, it doesn't show those.

00:41:53.860 --> 00:41:55.160
So this is pretty quick.

00:41:55.160 --> 00:41:55.640
Yeah.

00:41:55.640 --> 00:41:56.280
Pretty neat.

00:41:56.280 --> 00:41:57.080
Good one.

00:41:57.080 --> 00:41:57.500
Good one.

00:41:57.500 --> 00:41:58.500
Renee, how about you?

00:41:58.500 --> 00:41:59.000
Some extras?

00:41:59.000 --> 00:41:59.860
Sure.

00:41:59.860 --> 00:42:01.820
I wanted to make sure to mention my book.

00:42:01.820 --> 00:42:06.820
So just published and just out in Europe this week, actually, as a paperback, but it's been

00:42:06.820 --> 00:42:08.160
out since September in the US.

00:42:08.160 --> 00:42:12.540
SQL for Data Scientists, A Beginner's Guide for Building Data Sets for Analysis.

00:42:12.540 --> 00:42:18.540
So I mentioned earlier, you know, I wrote this book because I think a lot of students coming

00:42:18.540 --> 00:42:25.360
out of data science programs or people who are coming from maybe statistics background that

00:42:25.360 --> 00:42:28.580
are in data science might not have experience pulling the data.

00:42:28.580 --> 00:42:32.680
So in class, a lot of times you're given a clean spreadsheet to start with when you're building

00:42:32.680 --> 00:42:33.580
your predictive model.

00:42:33.580 --> 00:42:37.580
Then you get to the job and, you know, you sit down the first day and they say, great, build

00:42:37.580 --> 00:42:38.080
us a model.

00:42:38.080 --> 00:42:39.460
And you say, well, where's the data?

00:42:39.780 --> 00:42:41.880
It's in a raw form in the database.

00:42:41.880 --> 00:42:43.880
So you have to build your own data set.

00:42:43.880 --> 00:42:47.340
So that's what the purpose of the book is, to kind of get you from that point of when you

00:42:47.340 --> 00:42:52.260
have access to raw data to exploring and building your data set so that you can run it through

00:42:52.260 --> 00:42:53.280
your predictive model.

00:42:53.280 --> 00:42:56.100
So on the screen there, you see my website.

00:42:56.100 --> 00:43:00.280
And for people on the audio, it's SQL for datascientist.com.

00:43:01.020 --> 00:43:04.800
And you can go to different chapters on the website.

00:43:04.800 --> 00:43:07.480
And I have some example SQL, and you can also run it.

00:43:07.480 --> 00:43:11.220
So there's a SQLite database in the browser here.

00:43:11.220 --> 00:43:17.060
And so you can actually copy and paste some of the SQL on the page, click execute, and it

00:43:17.060 --> 00:43:18.500
shows up in a table down here.

00:43:18.500 --> 00:43:20.160
You can edit it and rerun it.

00:43:20.160 --> 00:43:24.100
So you get a little bit of practice with this, using the database in the book.

00:43:24.100 --> 00:43:24.560
Neat.

00:43:24.560 --> 00:43:25.360
Yeah.

00:43:25.360 --> 00:43:26.320
Cool book.

00:43:26.320 --> 00:43:28.220
And wow, SQLite in the browser.

00:43:28.220 --> 00:43:28.800
Very neat.

00:43:28.800 --> 00:43:29.780
Thank you.

00:43:29.780 --> 00:43:30.220
Yeah.

00:43:30.220 --> 00:43:30.880
Awesome.

00:43:31.000 --> 00:43:32.920
It's a book that definitely should exist.

00:43:32.920 --> 00:43:33.440
All right.

00:43:33.440 --> 00:43:37.160
Really quickly, I'm going to do a webcast with Paul Everitt.

00:43:37.160 --> 00:43:39.180
I haven't seen Paul in the audience today.

00:43:39.180 --> 00:43:39.840
Paul, where are you?

00:43:39.840 --> 00:43:40.740
No, I'm not sure.

00:43:40.740 --> 00:43:41.720
He might be working.

00:43:41.720 --> 00:43:46.220
But on November 23rd, I'm going to be doing a webcast around PyCharm.

00:43:46.220 --> 00:43:50.680
I've updated my PyCharm course with all sorts of good stuff.

00:43:50.680 --> 00:43:54.240
I haven't quite totally announced it yet because there's a few things I'm waiting, slightly

00:43:54.240 --> 00:43:59.160
more stable versions to come out of JetBrains to finish some of their data science tools,

00:43:59.160 --> 00:43:59.520
actually.

00:43:59.940 --> 00:44:01.480
And then I'll talk more about it.

00:44:01.480 --> 00:44:04.000
But we're doing a webcast in about a week or so.

00:44:04.000 --> 00:44:05.340
So that should be a lot of fun.

00:44:05.340 --> 00:44:07.820
And yeah, come check it out.

00:44:07.820 --> 00:44:10.200
Watch Paul and me make the code go.

00:44:10.200 --> 00:44:11.700
Two days before Thanksgiving.

00:44:11.700 --> 00:44:13.300
Yes, indeed.

00:44:13.300 --> 00:44:13.960
All right.

00:44:13.960 --> 00:44:15.160
That brings us to our joke.

00:44:15.160 --> 00:44:16.320
I need a joke.

00:44:16.660 --> 00:44:21.560
And the joke is a response to something that you posted on Twitter.

00:44:21.560 --> 00:44:27.040
Really appreciate my foresight of using lots of stuff comment as the message in Git commit.

00:44:27.040 --> 00:44:31.460
Well, it actually confused me because I did a Git rebase main and it said applying lots of

00:44:31.460 --> 00:44:31.760
stuff.

00:44:31.760 --> 00:44:37.260
And I thought it was a feature of Git rebase and it just happened to be my commit message.

00:44:38.260 --> 00:44:38.620
Yeah.

00:44:38.620 --> 00:44:40.120
It's like, oh, Git's gotten real casual.

00:44:40.120 --> 00:44:41.040
It's a lot of stuff.

00:44:41.980 --> 00:44:47.820
So Francois Voron said, time for a classic XKCD link here.

00:44:47.820 --> 00:44:48.420
Yeah.

00:44:48.420 --> 00:44:48.760
Oh, yeah.

00:44:48.760 --> 00:44:49.420
Yeah.

00:44:49.640 --> 00:44:55.860
And so this is like the commit history throughout the project as you get farther into it.

00:44:55.860 --> 00:45:01.380
So it starts out with very formal, proper comments like created main loop and timing control.

00:45:01.380 --> 00:45:04.640
The next commit is enabled config file parsing.

00:45:04.640 --> 00:45:09.800
And then starts to miscellaneous bug fixes and then code additions and edits.

00:45:09.800 --> 00:45:12.140
And then a branch, more code.

00:45:12.140 --> 00:45:16.220
You have code, just eight letter A's.

00:45:16.220 --> 00:45:18.400
It comes back with screaming.

00:45:18.880 --> 00:45:19.320
Exactly.

00:45:19.320 --> 00:45:22.080
Just A-D-K-F-J-S-L-K.

00:45:22.080 --> 00:45:23.220
Just a bunch of home row.

00:45:23.220 --> 00:45:25.700
And then my hands are typing words.

00:45:25.700 --> 00:45:27.040
And then just hands.

00:45:27.040 --> 00:45:33.400
And the title is, as a project drags on, my Git commit messages get less and less informative.

00:45:33.400 --> 00:45:34.480
We've all been there, right?

00:45:34.480 --> 00:45:34.900
Yeah.

00:45:34.900 --> 00:45:35.580
Yes.

00:45:35.580 --> 00:45:39.540
It happens to me with branch names too.

00:45:39.540 --> 00:45:44.320
Because if I'm working on one feature and push part of it and then I go and I'm still working

00:45:44.320 --> 00:45:46.920
on it, I like to use a new branch name.

00:45:47.400 --> 00:45:51.840
And I just, I can't, it's hard to come up with good branch names for a feature.

00:45:51.840 --> 00:45:52.700
I'm branching.

00:45:52.700 --> 00:45:54.760
Exactly.

00:45:54.760 --> 00:45:59.440
I try, I try to be more formal on the branches at least so I know I can delete them later.

00:45:59.440 --> 00:46:04.540
And so when I'm working on projects that are mostly just me, I'll create a GitHub issue

00:46:04.540 --> 00:46:08.820
and then create the branch name to be like a short version of the issue title and the issue

00:46:08.820 --> 00:46:09.340
number.

00:46:09.760 --> 00:46:13.520
So then when I commit back in, I can just look at the branch name and put a hash that

00:46:13.520 --> 00:46:17.200
number and it'll tag it in the commit on the issue and GitHub.

00:46:17.200 --> 00:46:17.760
Yeah.

00:46:17.760 --> 00:46:23.260
If I'm working with someone like a team, I might put like my name slash branch name.

00:46:23.260 --> 00:46:26.920
And then actually in some of the tools like source tree, you have like little expando widgets

00:46:26.920 --> 00:46:28.760
around that on the branches.

00:46:28.760 --> 00:46:32.500
So you can say these are Michael's branches and these are Renee's branches and so on.

00:46:32.740 --> 00:46:32.920
Yeah.

00:46:32.920 --> 00:46:34.340
We got into the habit of doing that too.

00:46:34.340 --> 00:46:37.440
It helps a lot to see right up front whose branch is this?

00:46:37.440 --> 00:46:38.020
Yeah.

00:46:38.020 --> 00:46:39.420
I can get, can get out of control.

00:46:39.420 --> 00:46:39.660
Right.

00:46:39.660 --> 00:46:40.220
All right.

00:46:40.220 --> 00:46:41.920
A quick couple follow-outs, Brian.

00:46:41.920 --> 00:46:44.840
Anthony says, the book looks great, Renee.

00:46:44.840 --> 00:46:45.640
I'll check it out.

00:46:45.640 --> 00:46:46.160
Great.

00:46:46.160 --> 00:46:46.620
Thank you.

00:46:46.620 --> 00:46:48.480
Chris May likes it as well.

00:46:48.480 --> 00:46:49.280
It's a great book idea.

00:46:49.280 --> 00:46:50.480
I do like it.

00:46:50.480 --> 00:46:53.360
Especially when I keep working long after I should have gone home.

00:46:53.360 --> 00:46:54.660
No, this is the joke.

00:46:54.660 --> 00:46:55.200
This is me.

00:46:55.200 --> 00:46:58.500
Especially after I keep working long after I should have gone home and gone home.

00:46:58.500 --> 00:46:59.240
Yeah, absolutely.

00:46:59.840 --> 00:47:04.720
And Sam, oops, forgot to stage this as a common message in my repositories.

00:47:04.720 --> 00:47:06.080
Nice.

00:47:06.080 --> 00:47:06.440
Indeed.

00:47:06.440 --> 00:47:07.900
So, cool.

00:47:07.900 --> 00:47:09.560
It was a fun episode.

00:47:09.560 --> 00:47:10.860
Thanks, Renee, for coming on.

00:47:10.860 --> 00:47:12.100
Yeah, thanks for having me.

00:47:12.100 --> 00:47:12.480
It's fun.

00:47:12.480 --> 00:47:15.260
Don't get to dive into Python too often.

00:47:15.260 --> 00:47:17.320
I mean, I'm using the same type of things over and over.

00:47:17.320 --> 00:47:19.760
So it's nice to see what's new and what's on the horizon.

00:47:19.760 --> 00:47:20.500
Awesome.

00:47:20.500 --> 00:47:20.800
Yep.

00:47:20.800 --> 00:47:21.400
Thanks, Mike.

00:47:21.400 --> 00:47:22.080
Thanks, Brian.

00:47:22.080 --> 00:47:22.660
Bye.

00:47:22.660 --> 00:47:22.720
Bye.

00:47:22.720 --> 00:47:22.740
Bye.

00:47:22.740 --> 00:47:22.800
Bye.

00:47:22.800 --> 00:47:23.400
Bye.